From c32e6e0648eb1ab28404766bdf5c3f64697ea394 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Tue, 13 Oct 2020 10:00:24 -0400 Subject: [PATCH 001/658] adds default set of interfaces and content_scripts --- .../inspectionProfiles/profiles_settings.xml | 5 + .idea/misc.xml | 81 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .../vuforia-spatial-remote-operator-addon.iml | 12 + .idea/workspace.xml | 60 + content_scripts/desktopAdapter.js | 973 ++++++ content_scripts/desktopCamera.js | 912 +++++ content_scripts/desktopRenderer.js | 310 ++ content_scripts/desktopStats.js | 100 + .../inspectionProfiles/profiles_settings.xml | 5 + interfaces/.idea/interfaces.iml | 12 + interfaces/.idea/misc.xml | 81 + interfaces/.idea/modules.xml | 8 + interfaces/.idea/vcs.xml | 6 + interfaces/.idea/workspace.xml | 61 + interfaces/desktopEditor/config.html | 387 +++ interfaces/desktopEditor/index.js | 256 ++ interfaces/desktopEditor/public/hololens.html | 85 + interfaces/pass/index.js | 689 ++++ interfaces/pass/public/ball.png | Bin 0 -> 14499 bytes interfaces/pass/public/clear.png | Bin 0 -> 13857 bytes interfaces/pass/public/index.html | 385 +++ interfaces/pass/public/jquery-1.11.0.min.js | 4 + .../pass/public/jquery-migrate-1.2.1.min.js | 2 + interfaces/pass/public/record.png | Bin 0 -> 17226 bytes interfaces/pass/public/slick/ajax-loader.gif | Bin 0 -> 4178 bytes interfaces/pass/public/slick/config.rb | 10 + interfaces/pass/public/slick/fonts/slick.eot | Bin 0 -> 2048 bytes interfaces/pass/public/slick/fonts/slick.svg | 14 + interfaces/pass/public/slick/fonts/slick.woff | Bin 0 -> 1380 bytes interfaces/pass/public/slick/slick-theme.css | 204 ++ interfaces/pass/public/slick/slick-theme.less | 168 + interfaces/pass/public/slick/slick-theme.scss | 194 ++ interfaces/pass/public/slick/slick.css | 119 + interfaces/pass/public/slick/slick.js | 3011 +++++++++++++++++ interfaces/pass/public/slick/slick.less | 100 + interfaces/pass/public/slick/slick.min.js | 1 + interfaces/pass/public/slick/slick.scss | 100 + interfaces/pass/public/stop.png | Bin 0 -> 4459 bytes interfaces/pass/public/twin.png | Bin 0 -> 35685 bytes 41 files changed, 8369 insertions(+) create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/vuforia-spatial-remote-operator-addon.iml create mode 100644 .idea/workspace.xml create mode 100644 content_scripts/desktopAdapter.js create mode 100644 content_scripts/desktopCamera.js create mode 100644 content_scripts/desktopRenderer.js create mode 100644 content_scripts/desktopStats.js create mode 100644 interfaces/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 interfaces/.idea/interfaces.iml create mode 100644 interfaces/.idea/misc.xml create mode 100644 interfaces/.idea/modules.xml create mode 100644 interfaces/.idea/vcs.xml create mode 100644 interfaces/.idea/workspace.xml create mode 100644 interfaces/desktopEditor/config.html create mode 100644 interfaces/desktopEditor/index.js create mode 100644 interfaces/desktopEditor/public/hololens.html create mode 100644 interfaces/pass/index.js create mode 100644 interfaces/pass/public/ball.png create mode 100644 interfaces/pass/public/clear.png create mode 100644 interfaces/pass/public/index.html create mode 100644 interfaces/pass/public/jquery-1.11.0.min.js create mode 100644 interfaces/pass/public/jquery-migrate-1.2.1.min.js create mode 100644 interfaces/pass/public/record.png create mode 100644 interfaces/pass/public/slick/ajax-loader.gif create mode 100644 interfaces/pass/public/slick/config.rb create mode 100644 interfaces/pass/public/slick/fonts/slick.eot create mode 100644 interfaces/pass/public/slick/fonts/slick.svg create mode 100644 interfaces/pass/public/slick/fonts/slick.woff create mode 100644 interfaces/pass/public/slick/slick-theme.css create mode 100644 interfaces/pass/public/slick/slick-theme.less create mode 100644 interfaces/pass/public/slick/slick-theme.scss create mode 100644 interfaces/pass/public/slick/slick.css create mode 100644 interfaces/pass/public/slick/slick.js create mode 100644 interfaces/pass/public/slick/slick.less create mode 100644 interfaces/pass/public/slick/slick.min.js create mode 100644 interfaces/pass/public/slick/slick.scss create mode 100644 interfaces/pass/public/stop.png create mode 100644 interfaces/pass/public/twin.png diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..0eefe328 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..4dab1693 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..963cc104 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vuforia-spatial-remote-operator-addon.iml b/.idea/vuforia-spatial-remote-operator-addon.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/.idea/vuforia-spatial-remote-operator-addon.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..cea37a82 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + 1602597440248 + + + + + + + + + \ No newline at end of file diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js new file mode 100644 index 00000000..ce8aa345 --- /dev/null +++ b/content_scripts/desktopAdapter.js @@ -0,0 +1,973 @@ +createNameSpace('realityEditor.device.desktopAdapter'); + +/** + * @fileOverview realityEditor.device.desktopAdapter.js + * If the editor frontend is loaded on a desktop browser, re-maps some native functions, adjusts some CSS, and + * waits for a connection from a mobile editor that will stream matrices here + */ + +var mRot = function(pitch, roll, yaw) { + return realityEditor.gui.ar.utilities.getMatrixFromQuaternion(realityEditor.gui.ar.utilities.getQuaternionFromPitchRollYaw(pitch * Math.PI / 180, roll * Math.PI / 180, yaw * Math.PI / 180)); +}; + +(function(exports) { + // Automatically connect to all discovered reality zones + const AUTO_ZONE_CONNECT = true; + + /** + * @type {number} - the handle for the setInterval for mobile editors to connect to desktops + */ + var broadcastInterval; + + /** + * @type {boolean} - when paused, desktops ignore matrices received from mobile editors and use their own + */ + var isPaused = false; + + /** + * @type {Dropdown} - DOM element to start connecting to Reality Zones and select among possible connections + */ + var zoneDropdown; + + /** + * @type {Dropdown} - DOM element to start connecting to phone matrices and select among possible connections + */ + var deviceDropdown; + + var currentConnectedDeviceIP; + + // polyfill for requestAnimationFrame to provide a smooth update loop + var requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || function(cb) {setTimeout(cb, 17);}; + + // holds the most recent set of objectId/matrix pairs so that they can be rendered on the next frame + var visibleObjects = {}; + + // Refresh after 1 hour of no activity + const IDLE_TIMEOUT_MS = 60 * 60 * 1000; + // Stores timeout id for idle detection + let idleTimeout = null; + let savedZoneSocketIPs = []; + + /** + * @type {CallbackHandler} + */ + var callbackHandler = new realityEditor.moduleCallbacks.CallbackHandler('device/desktopAdapter'); + + /** + * Adds a callback function that will be invoked when the specified function is called + * @param {string} functionName + * @param {function} callback + */ + function registerCallback(functionName, callback) { + if (!callbackHandler) { + callbackHandler = new realityEditor.moduleCallbacks.CallbackHandler('device/desktopAdapter'); + } + callbackHandler.registerCallback(functionName, callback); + } + + /** + * initialize the desktop adapter only if we are running on a desktop environment + */ + function initService() { + if (realityEditor.device.utilities.isDesktop()) { + + restyleForDesktop(); + modifyGlobalNamespace(); + + setTimeout(function() { + addSocketListeners(); // HACK. this needs to happen after realtime module finishes loading + }, 100); + + addZoneDiscoveredListener(); + addZoneControlListener(); + + // TODO: is this really the best way to do this? data should come from server, not browser storage + visibleObjects = JSON.parse(window.localStorage.getItem('realityEditor.desktopAdapter.savedMatrices') || '{}'); + + savedZoneSocketIPs = JSON.parse(window.localStorage.getItem('realityEditor.desktopAdapter.savedZoneSocketIPs') || '[]'); + + // Reset savedZoneSocketIPs so that they are only persisted on idle refresh + window.localStorage.setItem('realityEditor.desktopAdapter.savedZoneSocketIPs', '[]'); + + if (savedZoneSocketIPs.length > 0) { + // Use the normal broadcast/receive message flow so that we + // know the savedZoneSocketIP still exists + setTimeout(function() { + startBroadcastingZoneConnect(); + setTimeout(function() { + stopBroadcastingZoneConnect(); + }, 1000); + }, 1000); + } + + var desktopProjectionMatrix = projectionMatrixFrom(25, -window.innerWidth / window.innerHeight, 0.1, 300000); + console.log('desktop matrix', desktopProjectionMatrix); + + // noinspection JSSuspiciousNameCombination + globalStates.height = window.innerWidth; + // noinspection JSSuspiciousNameCombination + globalStates.width = window.innerHeight; + + realityEditor.gui.ar.setProjectionMatrix(desktopProjectionMatrix); + + // add a keyboard listener to toggle visibility of the zone/phone discovery buttons + realityEditor.device.keyboardEvents.registerCallback('keyUpHandler', function(params) { + if (params.event.code === 'KeyV') { + + if (zoneDropdown) { + if (zoneDropdown.dom.style.display !== 'none') { + zoneDropdown.dom.style.display = 'none'; + realityEditor.device.desktopStats.hide(); // also toggle stats + } else { + zoneDropdown.dom.style.display = ''; + realityEditor.device.desktopStats.show(); + } + } + + if (deviceDropdown) { + if (deviceDropdown.dom.style.display !== 'none') { + deviceDropdown.dom.style.display = 'none'; + } else { + deviceDropdown.dom.style.display = ''; + } + } + } + }); + + update(); + + } else { + + // TODO: replace this with a better version that isn't reliant on editor being open at same time + // for NON desktop editors, they should broadcast their visible object matrices when enabled + realityEditor.gui.ar.draw.addUpdateListener(function(visibleObjects) { + if (globalStates.matrixBroadcastEnabled) { + broadcastMatrices(visibleObjects); + } + }); + } + } + + /** + * Builds a projection matrix from field of view, aspect ratio, and near and far planes + */ + function projectionMatrixFrom(vFOV, aspect, near, far) { + console.log(Math.DEG2RAD); + var top = near * Math.tan((Math.PI / 180) * 0.5 * vFOV ); + console.log('top', top); + var height = 2 * top; + var width = aspect * height; + var left = -0.5 * width; + console.log(vFOV, aspect, near, far); + return makePerspective( left, left + width, top, top - height, near, far ); + } + + /** + * Helper function for creating a projection matrix + */ + function makePerspective ( left, right, top, bottom, near, far ) { + + var te = []; + 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 ); + + console.log(x, y, a, b, c); + + 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 te; + + } + + function sendZoneCommand(command) { + realityEditor.network.realtime.sendMessageToSocketSet('realityZones', 'message', command); + } + + function addZoneControlListener() { + document.addEventListener('keydown', function(event) { + switch (event.key) { + case 'n': + sendZoneCommand('toggleLines'); + break; + case 'q': + sendZoneCommand('resetLines'); + break; + case 'm': + sendZoneCommand('toggleXRayView'); + break; + case 'b': + sendZoneCommand('toggleSkeletons'); + break; + case ' ': + sendZoneCommand('toggleDemoMode'); + break; + } + }); + } + + /** + * Adjust visuals for desktop rendering -> set background color and add "Waiting for Connection..." indicator + */ + function restyleForDesktop() { + document.body.style.backgroundColor = 'rgb(50,50,50)'; + document.getElementById('canvas').style.backgroundColor = 'transparent'; + document.getElementById('canvas').style.transform = 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -1, 1)'; // set Z to 1 to render in front + document.getElementById('canvas').style.pointerEvents = 'none'; + + var DISABLE_SAFE_MODE = true; + if (!DISABLE_SAFE_MODE) { + if (window.outerWidth !== document.body.offsetWidth) { + alert('Reset browser zoom level to get accurate calculations'); + } + } + + // TODO: remove memory bar from pocket... instead maybe be able to save camera positions? + + createZoneConnectionDropdown(); + } + + /** + * Re-maps native app calls to functions within this file. + * E.g. calling realityEditor.app.setPause() will be rerouted to this file's setPause() function. + * @todo Needs to be manually modified as more native calls are added. Add one switch case per native app call. + */ + function modifyGlobalNamespace() { + + // set up object structure if it doesn't exist yet + window.webkit = { + messageHandlers: { + realityEditor: {} + } + }; + + // intercept postMessage calls to the messageHandlers and manually handle each case by functionName + window.webkit.messageHandlers.realityEditor.postMessage = function(messageBody) { + // console.log('intercepted message to native -> ', messageBody); + + switch (messageBody.functionName) { + case 'setPause': + setPause(); + break; + case 'setResume': + setResume(); + break; + case 'getVuforiaReady': + getVuforiaReady(messageBody.arguments); + break; + case 'sendUDPMessage': + sendUDPMessage(messageBody.arguments); + break; + // case 'getUDPMessages': + // getUDPMessages(messageBody.callback); + default: + // console.log('could not find desktop implementation of app.' + messageBody.functionName); + return; + } + }; + + // polyfill webkit functions on Chrome browser + if (typeof window.webkitConvertPointFromPageToNode === 'undefined') { + console.log('Polyfilling webkitConvertPointFromPageToNode for this browser'); + + polyfillWebkitConvertPointFromPageToNode(); + + var ssEl = document.createElement('style'), + css = '.or{position:absolute;opacity:0;height:33.333%;width:33.333%;top:0;left:0}.or.r-2{left:33.333%}.or.r-3{left:66.666%}.or.r-4{top:33.333%}.or.r-5{top:33.333%;left:33.333%}.or.r-6{top:33.333%;left:66.666%}.or.r-7{top:66.666%}.or.r-8{top:66.666%;left:33.333%}.or.r-9{top:66.666%;left:66.666%}'; + ssEl.type = 'text/css'; + (ssEl.styleSheet) ? + ssEl.styleSheet.cssText = css : + ssEl.appendChild(document.createTextNode(css)); + document.getElementsByTagName('head')[0].appendChild(ssEl); + } + + pocketBegin = [4809.935578545477, -21.448650897337046, 0.4699633267584691, 0.46996614495062317, -44.91500625468903, 4866.661791176597, 0.11589206604418023, 0.11589146054651356, 1868.0710976511762, 151.4880583152451, 3.796594149786673, 3.7967717726783503, 253912.5079441294, -209710.60820716852, 4880.993541501766, 4881.118759195814]; + + realityEditor.gui.ar.draw.correctedCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + + // realityEditor.gui.ar.draw.groundPlaneMatrix = realityEditor.gui.ar.draw.correctedCameraMatrix; // ground plane just points to camera matrix on desktop + + // rotateX = mRot(0,0,-90); + + // rotateX = mRot(0,-90,0); + + // rotateX = mFlipYZ; + // realityEditor.gui.ar.draw.rotateX_desktopWorld = rotateX; + + rotateX = [ + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1 + ]; + realityEditor.gui.ar.draw.rotateX = rotateX; + + // desktopFrameTransform = mRot(0,0,90); // fixes the rotation but too late, not the resulting translation + + // desktopFrameTransform = mFlipYZ; + } + + /** + * Based off of https://gist.github.com/Yaffle/1145197 with modifications to + * support more complex matrices + */ + function polyfillWebkitConvertPointFromPageToNode() { + const identity = new DOMMatrix([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + + if (!window.WebKitPoint) { + window.WebKitPoint = DOMPoint; + } + + function getTransformationMatrix(element) { + var transformationMatrix = identity; + var x = element; + + while (x !== undefined && x !== x.ownerDocument.documentElement) { + var computedStyle = window.getComputedStyle(x); + var transform = computedStyle.transform || "none"; + var c = transform === "none" ? identity : new DOMMatrix(transform); + + transformationMatrix = c.multiply(transformationMatrix); + x = x.parentNode; + } + + // Normalize current matrix to have m44=1 (w = 1). Math does not work + // otherwise because nothing knows how to scale based on w + let baseArr = transformationMatrix.toFloat64Array(); + baseArr = baseArr.map(b => b / baseArr[15]); + transformationMatrix = new DOMMatrix(baseArr); + + var w = element.offsetWidth; + var h = element.offsetHeight; + var i = 4; + var left = +Infinity; + var top = +Infinity; + while (--i >= 0) { + var p = transformationMatrix.transformPoint(new DOMPoint(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, 0)); + if (p.x < left) { + left = p.x; + } + if (p.y < top) { + top = p.y; + } + } + var rect = element.getBoundingClientRect(); + transformationMatrix = identity.translate(window.pageXOffset + rect.left - left, window.pageYOffset + rect.top - top, 0).multiply(transformationMatrix); + return transformationMatrix; + } + + window.convertPointFromPageToNode = window.webkitConvertPointFromPageToNode = function (element, point) { + let mati = getTransformationMatrix(element).inverse(); + // This involves a lot of math, sorry. + // Given $v = M^{-1}p$ we have p.x, p.y, p.w, M^{-1}, and know that v.z + // should be equal to 0. + // Solving for p.z we get the following: + let projectedZ = -(mati.m13 * point.x + mati.m23 * point.y + mati.m43) / mati.m33; + return mati.transformPoint(new DOMPoint(point.x, point.y, projectedZ)); + }; + + window.convertPointFromNodeToPage = function (element, point) { + return getTransformationMatrix(element).transformPoint(point); + }; + } + + /** + * Adds socket.io listeners for matrix streams and UDP messages necessary to setup editor without mobile environment + * (e.g. object discovery) + */ + function addSocketListeners() { + + realityEditor.network.realtime.addDesktopSocketMessageListener('/matrix/visibleObjects', function(msgContent) { + console.log('received matrix message: ' + msgContent); + // serverSocket.on('/matrix/visibleObjects', function(msgContent) { + if (!isPaused) { + if (msgContent.ip && msgContent.port) { + if (isConnectedDeviceIP(msgContent.ip, msgContent.port)) { //or if they are coming from the zone ip? + visibleObjects = msgContent.visibleObjects; // new matrices update as fast as they can be received + + if (saveNextMatrices) { + window.localStorage.setItem('realityEditor.desktopAdapter.savedMatrices', JSON.stringify(visibleObjects)); + saveNextMatrices = false; + } + } + } + } + }); + + realityEditor.network.addObjectDiscoveredCallback(function(object, objectKey) { + console.log('object discovered: ' + objectKey + ' (desktop)'); + + // make objects show up by default at the origin + if (object.matrix.length === 0) { + // if (object.isWorldObject) { + console.log('putting object ' + object.name + ' at the origin'); + object.matrix = [ + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 + ]; //realityEditor.gui.ar.utilities.newIdentityMatrix(); + } + + // set initial value to the last object matrix stored value on server + if (object.matrix && object.matrix.length === 16) { + visibleObjects[objectKey] = object.matrix; + + // TODO: eliminate ALL THE HACKS HERE + if (!object.isWorldObject) { + + // realityEditor.gui.ar.draw.rotateX = mRot(20,45,-70); // hard-coded for feeder + + // var rotatedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(object.matrix); + + // if its an object target, we might need to flip and rotate it instead? + // var rotatedObjectMatrix = []; //realityEditor.gui.ar.utilities.copyMatrix(object.matrix); + // var rotation2d = mRot(0,180,90); + // realityEditor.gui.ar.utilities.multiplyMatrix(rotation2d, object.matrix, rotatedObjectMatrix); + + + var rotatedObjectMatrix = []; + var rotation3d = [ + 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 + ]; + realityEditor.gui.ar.utilities.multiplyMatrix(rotation3d, object.matrix, rotatedObjectMatrix); + // var rotation2d = mRot(0,0,0); + // realityEditor.gui.ar.utilities.multiplyMatrix(rotation2d, rotatedObjectMatrix, rotatedObjectMatrix); + var oldZ = rotatedObjectMatrix[14]; + rotatedObjectMatrix[14] = -rotatedObjectMatrix[13]; + rotatedObjectMatrix[13] = -oldZ; + + console.log('object ' + objectKey + 'is at (' + rotatedObjectMatrix[12] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[13] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[14] / rotatedObjectMatrix[15] + ')'); + + var HACK_FEEDER_ORIENTATION = true; + if (HACK_FEEDER_ORIENTATION) { + if (objectKey === 'kepwareBox4Qimhnuea3n6') { // the feeder + // var feederRotation = mRot(20, 45, -70); + let feederRotation = mRot(-30, 120, 70); // -30, 120, 70 works pretty well + let matrixCopy = realityEditor.gui.ar.utilities.copyMatrix(rotatedObjectMatrix); + realityEditor.gui.ar.utilities.multiplyMatrix(feederRotation, matrixCopy, rotatedObjectMatrix); + + } else if (objectKey.indexOf('kepwareBox') > -1) { + let supplyRotation = mRot(60, 5, 70); /// 60, 45, 90 is close // 60, 5, 70 is better + let matrixCopy = realityEditor.gui.ar.utilities.copyMatrix(rotatedObjectMatrix); + realityEditor.gui.ar.utilities.multiplyMatrix(supplyRotation, matrixCopy, rotatedObjectMatrix); + } + } + + visibleObjects[objectKey] = rotatedObjectMatrix; + } else { + + visibleObjects[objectKey] = realityEditor.gui.ar.utilities.copyMatrix(object.matrix); + + // var flippedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(object.matrix); + // flippedObjectMatrix[5] *= -1; + // flippedObjectMatrix[10] *= -1; + // + // console.log(flippedObjectMatrix); + // + // var rotatedFlipped = []; + // realityEditor.gui.ar.utilities.multiplyMatrix(mFlipYZ, flippedObjectMatrix, rotatedFlipped); + // + // console.log(rotatedFlipped); + + // visibleObjects[objectKey] = rotatedFlipped; + + // visibleObjects[objectKey] = [ + // 1, 0, 0, 0, + // 0, 0, -1, 0, + // 0, -1, 0, 0, + // 0, 0, 0, 1 + // ] + } + + } + + // subscribe to new object matrices to update where the object is in the world + realityEditor.network.realtime.subscribeToObjectMatrices(objectKey, function(data) { + if (globalStates.freezeButtonState) { return; } + + var msgData = JSON.parse(data); + if (msgData.objectKey === objectKey && msgData.propertyPath === 'matrix') { + + // emit an event if this is a newly "discovered" matrix + if ((!object.matrix || object.matrix.length !== 16) && msgData.newValue.length === 16) { + console.log('discovered a matrix for an object that didn\'t have one before'); + callbackHandler.triggerCallbacks('objectMatrixDiscovered', {objectKey: msgData.objectKey}); + } + + // TODO: fix these calculations too + var rotatedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(msgData.newValue); + + // var rotatedObjectMatrix = []; + // var rotation3d = [ + // 1, 0, 0, 0, + // 0, -1, 0, 0, + // 0, 0, -1, 0, + // 0, 0, 0, 1 + // ]; + // realityEditor.gui.ar.utilities.multiplyMatrix(rotation3d, msgData.newValue, rotatedObjectMatrix); + var oldZ = rotatedObjectMatrix[14]; + rotatedObjectMatrix[14] = -rotatedObjectMatrix[13]; + rotatedObjectMatrix[13] = -oldZ; + + object.matrix = rotatedObjectMatrix; + visibleObjects[msgData.objectKey] = rotatedObjectMatrix; + + + // compensate for rotateX by flipping X translation + // msgData.newValue[14] *= -1; + // msgData.newValue[10] *= -1; + + // object.matrix = msgData.newValue; + // visibleObjects[msgData.objectKey] = msgData.newValue; + // console.log('updated matrix'); + } + }); + }); + + realityEditor.network.realtime.addDesktopSocketMessageListener('udpMessage', function(msgContent) { + + if (typeof msgContent.id !== 'undefined' && + typeof msgContent.ip !== 'undefined') { + realityEditor.network.addHeartbeatObject(msgContent); + } + + // TODO: understand what this is doing and if it's still necessary + if (!currentConnectedDeviceIP) { + if (typeof msgContent.projectionMatrix !== 'undefined') { + if (typeof msgContent.ip !== 'undefined' && typeof msgContent.port !== 'undefined') { + + if (isConnectedDeviceIP(msgContent.ip, msgContent.port)) { + + window.localStorage.setItem('realityEditor.desktopAdapter.projectionMatrix', JSON.stringify(msgContent.projectionMatrix)); + window.localStorage.setItem('realityEditor.desktopAdapter.realProjectionMatrix', JSON.stringify(msgContent.realProjectionMatrix)); + console.log('projection matrix:', msgContent.realProjectionMatrix); + + console.log('msgContent.projectionMatrix', msgContent); + console.log('finished connecting to ' + msgContent); + currentConnectedDeviceIP = msgContent.ip; + + _saveMatrixInterval = setInterval(function() { + saveNextMatrices = true; + }, 5000); + } + } + } + } + + if (typeof msgContent.action !== 'undefined') { + console.log(msgContent.action); + if (typeof msgContent.action === 'string') { + try { + msgContent.action = JSON.parse(msgContent.action); + } catch (e) { + // console.log('dont need to parse'); + } + } + realityEditor.network.onAction(msgContent.action); + } + + // forward the message to a generic message handler that various modules use to subscribe to different messages + realityEditor.network.onUDPMessage(msgContent); + + }); + + } + + var _saveMatrixInterval = null; + var saveNextMatrices = false; + + function areIPsEqual(ip1, ip2) { + if (ip1 === ip2) { + return true; + } + + // try popping off the http, etc + var split1 = ip1.split('://'); + var split2 = ip2.split('://'); + return (split1[split1.length - 1] === split2[split2.length - 1]); + } + + function isConnectedDeviceIP(ip, port) { + // return (currentConnectedDeviceIP && currentConnectedDeviceIP === ip); + var deviceMessageIsFrom = ip; + if (port) { + deviceMessageIsFrom += ':' + port; + } + + if (deviceDropdown.selected) { + var selectedDevice = deviceDropdown.selected.element.id; + // console.log('selected device is ' + selectedDevice); + + if (areIPsEqual(selectedDevice, deviceMessageIsFrom)) { + return true; + } + } + + return false; + } + + /** + * The 60 FPS render loop. Smoothly calls realityEditor.gui.ar.draw.update to render the most recent visibleObjects + * Also smoothly updates camera postion when paused + */ + function update() { + + // send the visible object matrices to connected reality zones, if any // TODO: there actually only needs to be one, not a set... + if (realityEditor.network.realtime.getSocketIPsForSet('realityZones').length > 0) { + sendMatricesToRealityZones(); + } + + // trigger main update function after a 100ms delay, to synchronize with the approximate lag of the realityZone image processing + // updateAfterDelay(visibleMatrixCopy, 100); + updateAfterDelay(visibleObjects, 100); + + // repeat loop on next render + requestAnimationFrame(update); + } + + function updateAfterDelay(matrices, delayInMs) { + setTimeout(function() { + realityEditor.gui.ar.draw.update(matrices); + }, delayInMs); + } + + /** + * Sends the current matrix set to all connected desktop editors + * @param {Object.>} visibleObjects + */ + function broadcastMatrices(visibleObjects) { + var eventName = '/matrix/visibleObjects'; + var messageBody = {visibleObjects: visibleObjects, ip: window.location.hostname, port: window.location.port}; + realityEditor.network.realtime.sendMessageToSocketSet('desktopEditors', eventName, messageBody); + } + + /** + * Creates a switch that, when activated, begins broadcasting UDP messages + * to discover any Reality Zones in the network for volumetric rendering + */ + function createZoneConnectionDropdown() { + if (!zoneDropdown) { + + var textStates = { + collapsedUnselected: 'Search for Reality Zones', + expandedEmpty: 'Searching for Zones...', + expandedOptions: 'Select a Zone', + selected: 'Connected: ' + }; + + zoneDropdown = new realityEditor.gui.dropdown.Dropdown('zoneDropdown', textStates, {left: '30px', top: '30px'}, document.body, true, onZoneSelectionChanged, onZoneExpandedChanged); + + } + } + + function onZoneSelectionChanged(selected) { + // TODO: connect to the web socket for the zone at that IP + + if (selected && selected.element) { + var zoneIp = selected.element.id; + if (zoneIp) { + establishConnectionWithZone(zoneIp); + } + } + } + + function onZoneExpandedChanged(isExpanded) { + if (isExpanded) { + // start a loop to ping the zones every 3 seconds, and listen for their responses + startBroadcastingZoneConnect(); // TODO: do this from the start, dont wait until click on dropdown + } else { + stopBroadcastingZoneConnect(); + } + } + + var zoneBroadcastInterval = null; + var isSearchingForZones = false; + + function stopBroadcastingZoneConnect() { + clearInterval(zoneBroadcastInterval); + zoneBroadcastInterval = null; + isSearchingForZones = false; + } + + /** + * Starts a heartbeat message loop that broadcasts out zoneConnect messages to volumetric rendering zones in the network + * Also creates a UDP listener for zoneResponse action messages, which upon receiving will establish a web socket + */ + function startBroadcastingZoneConnect() { + if (isSearchingForZones) { return; } + + isSearchingForZones = true; + + // pulse visuals once immediately to show it has activated + // send one immediately to establish the connection faster + sendSingleZoneConnectBroadcast(); + pulseButton(zoneDropdown.textDiv); + + // every 3 seconds, send out another UDP message and pulse visuals to show it is still in progress + zoneBroadcastInterval = setInterval(function() { + sendSingleZoneConnectBroadcast(); + pulseButton(zoneDropdown.textDiv); + }, 3000); + } + + function pulseButton(domElement) { + domElement.classList.add('connectionSwitchPulse'); + setTimeout(function() { + domElement.classList.remove('connectionSwitchPulse'); + }, 1500); + } + + function addZoneDiscoveredListener() { + // when an action message is detected with a zoneResponse, establish a web socket connection with that zone + realityEditor.network.addUDPMessageHandler('action', function(message) { + if (!isSearchingForZones) { return; } + if (typeof message.action !== 'undefined') { + if (typeof message.action.action !== 'undefined') { + message = message.action; + } + } + if (typeof message.action !== 'undefined' && message.action === 'zoneDiscovered' && message.ip && message.port) { + // console.log('zoneDiscoveredListener', message); + + // create a new web socket with the zone at the specified address received over UDP + var potentialZoneAddress = 'http://' + message.ip + ':' + message.port; + + var alreadyContainsIP = zoneDropdown.selectables.map(function(selectableObj) { + return selectableObj.id; + }).indexOf(potentialZoneAddress) > -1; + + if (!alreadyContainsIP) { + zoneDropdown.addSelectable(potentialZoneAddress, potentialZoneAddress); + } + if (savedZoneSocketIPs.includes(potentialZoneAddress) || AUTO_ZONE_CONNECT) { + establishConnectionWithZone(potentialZoneAddress); + } + } + }); + } + + function establishConnectionWithZone(zoneIp) { + var socketsIps = realityEditor.network.realtime.getSocketIPsForSet('realityZones'); + + // only establish a new connection if we don't already have one with that server + if (socketsIps.indexOf(zoneIp) < 0) { + console.log('zoip', zoneIp); + let socketId = zoneIp.includes('10.10.10.105') ? 'primary' : 'secondary'; + + // zoneConnectionSwitch.innerHTML = '[CONNECTED TO ZONE]'; + + // create the web socket to send matrices to the discovered reality zone + realityEditor.network.realtime.createSocketInSet('realityZones', zoneIp, function(socket) { + // on connect + realityEditor.network.realtime.sendMessageToSocketSet('realityZones', 'name', { + type: 'viewer', + editorId: globalStates.tempUuid + }); + + socket.on('image', function(data) { + realityEditor.gui.ar.desktopRenderer.processImageFromSource(socketId, data); + }); + + socket.on('error', function(data) { + console.warn(data); + }); + + }); + + // realityEditor.network.realtime.addDesktopSocketMessageListener('image', function(msgContent) { + // realityEditor.gui.ar.desktopRenderer.renderImageInBackground(msgContent); + // }); + // + // realityEditor.network.realtime.addDesktopSocketMessageListener('error', function(msgContent) { + // console.warn('ERROR (Reality Zone ' + zoneIp + '): ' + msgContent); + // }); + // + // realityEditor.network.realtime.addDesktopSocketMessageListener('error', function(msgContent) { + // console.warn('ERROR (Reality Zone ' + zoneIp + '): ' + msgContent); + // }); + + // TODO: immediately sends the most recent matrices as a test, but in future just send in render loop? + sendMatricesToRealityZones(); + } + } + + /** + * Broadcasts a zoneConnect message over UDP. + * Includes the editorId in case the Reality Zone wants to adjust its response based on the ID of the client who sent the message. + */ + function sendSingleZoneConnectBroadcast() { + + var advertiseConnectionMessage = { + action: 'advertiseEditor', + clientType: 'desktop', // or 'mobile' in the future when we want to + resolution: { + width: window.innerWidth, + height: window.innerHeight + }, + editorId: globalStates.tempUuid + }; + realityEditor.app.sendUDPMessage(advertiseConnectionMessage); + + } + + /** + * Sends the visible matrices to any reality zones that the desktop client has formed a web socket connection with. + * @todo: should we just send the camera position instead? or just one matrix (one matrix -> one image) + */ + function sendMatricesToRealityZones() { + // TODO: move this to the update loop and send every frame if we have established a connection with a reality zone + var messageBody = { + cameraPoseMatrix: realityEditor.gui.ar.draw.correctedCameraMatrix, // doesnt matter + realProjectionMatrix: globalStates.realProjectionMatrix, // doesnt matter + projectionMatrix: globalStates.projectionMatrix, // uses this + visibleObjectMatrices: realityEditor.gui.ar.draw.visibleObjects, // this should contain modelViewMatrix + resolution: { + width: window.innerWidth, + height: window.innerHeight + }, + editorId: globalStates.tempUuid + }; + + realityEditor.network.realtime.sendMessageToSocketSet('realityZones', 'poseVuforiaCamera', messageBody); + } + + /** + * Overrides realityEditor.app.setPause -> ignores matrix stream while paused + */ + function setPause() { + isPaused = true; + } + + /** + * Overrides realityEditor.app.setResume -> stops ignoring matrix stream. + * Also resets any camera position information so that next time you freeze it doesn't jump to old camera position + */ + function setResume() { + isPaused = false; + } + + function createNativeAPISocket() { + // lazily instantiate the socket to the server if it doesn't exist yet + var socketsIps = realityEditor.network.realtime.getSocketIPsForSet('nativeAPI'); + // var hostedServerIP = 'http://127.0.0.1:' + window.location.port; + var hostedServerIP = 'http://' + window.location.host; //'http://127.0.0.1:' + window.location.port; + + if (socketsIps.indexOf(hostedServerIP) < 0) { + realityEditor.network.realtime.createSocketInSet('nativeAPI', hostedServerIP); + realityEditor.network.realtime.addDesktopSocketMessageListener('test', function(message) { + console.log('received message from socketMessageListener', message); + }); + } + } + + function sendUDPMessage(args) { + createNativeAPISocket(); + realityEditor.network.realtime.sendMessageToSocketSet('nativeAPI', '/nativeAPI/sendUDPMessage', args.message); + } + + function getVuforiaReady(_args) { + // just immediately call the callback because vuforia will never load on the desktop + + // this is the only functionality we still need from the original callback (realityEditor.app.callbacks.vuforiaIsReady) + realityEditor.app.getUDPMessages('realityEditor.app.callbacks.receivedUDPMessage'); + } + + /** + * Broadcasts this editor's projection matrix to all desktop editors, so that they can use it. + * Checks if there are any objects from new servers, and establishes a socket connection to their desktop clients. + * Should be called on mobile editors only. + */ + function broadcastEditorInformation() { + + realityEditor.app.sendUDPMessage({ + action: 'mobileEditorDiscovered', + ip: window.location.hostname, + port: parseInt(window.location.port), + // uuid: globalStates.tempUuid + projectionMatrix: globalStates.projectionMatrix, + realProjectionMatrix: globalStates.realProjectionMatrix + }); + + var ipList = []; + realityEditor.forEachObject(function(object, _objectKey) { + if (ipList.indexOf(object.ip) === -1) { + ipList.push(object.ip); + } + }); + + var desktopEditorPort = 8081; + ipList.forEach(function(ip) { + var potentialDesktopEditorAddress = 'http://' + ip + ':' + desktopEditorPort; + var socketsIps = realityEditor.network.realtime.getSocketIPsForSet('desktopEditors'); + if (socketsIps.indexOf(potentialDesktopEditorAddress) < 0) { + realityEditor.network.realtime.createSocketInSet('desktopEditors', potentialDesktopEditorAddress); + } + }); + } + + function resetIdleTimeout() { + if (idleTimeout) { + clearTimeout(idleTimeout); + } + idleTimeout = setTimeout(storeZonesAndRefresh, IDLE_TIMEOUT_MS); + } + + /** + * Store enough data to refresh without losing a significant amount of + * state then refresh + */ + function storeZonesAndRefresh() { + // Zone connections to restore background image + const zoneSocketIPs = realityEditor.network.realtime.getSocketIPsForSet('realityZones'); + window.localStorage.setItem('realityEditor.desktopAdapter.savedZoneSocketIPs', JSON.stringify(zoneSocketIPs)); + window.location.reload(); + } + + exports.updateObjectMatrix = function(objectKey, pitch, roll, yaw) { + var object = realityEditor.getObject(objectKey); + var rotatedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(object.matrix); + + // var rotatedObjectMatrix = []; + var rotation3d = [ + 1, 0, 0, 0, + 0, 0, -1, 0, + 0, -1, 0, 0, + 0, 0, 0, 1 + ]; + realityEditor.gui.ar.utilities.multiplyMatrix(rotation3d, object.matrix, rotatedObjectMatrix); + var rotation2d = mRot(pitch, roll, yaw); + realityEditor.gui.ar.utilities.multiplyMatrix(rotation2d, rotatedObjectMatrix, rotatedObjectMatrix); + var oldZ = rotatedObjectMatrix[14]; + rotatedObjectMatrix[14] = -rotatedObjectMatrix[13]; + rotatedObjectMatrix[13] = -oldZ; + + console.log('object ' + objectKey + 'is at (' + rotatedObjectMatrix[12] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[13] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[14] / rotatedObjectMatrix[15] + ')'); + + visibleObjects[objectKey] = rotatedObjectMatrix; + }; + + // Currently unused + exports.registerCallback = registerCallback; + + exports.resetIdleTimeout = resetIdleTimeout; + + // this happens only for desktop editors + realityEditor.addons.addCallback('init', initService); +}(realityEditor.device.desktopAdapter)); diff --git a/content_scripts/desktopCamera.js b/content_scripts/desktopCamera.js new file mode 100644 index 00000000..8a181b5e --- /dev/null +++ b/content_scripts/desktopCamera.js @@ -0,0 +1,912 @@ +createNameSpace('realityEditor.device.desktopCamera'); + +/** + * @fileOverview realityEditor.device.desktopCamera.js + * Responsible for manipulating the camera position and resulting view matrix, on remote desktop clients + */ + +(function() { + + var cameraPosition = [735, -1575, -162]; //[1000, -500, 500]; + var cameraTargetPosition = [0, 0, 0]; + var previousTargetPosition = [0, 0, 0]; + var currentDistanceToTarget = 500; + var isFollowingObjectTarget = false; + var closestObjectLog = null; + + var targetOnLoad = 'kepwareBox4Qimhnuea3n6'; + + var DEBUG_SHOW_LOGGER = false; + var DEBUG_REMOVE_KEYBOARD_CONTROLS = false; + var DEBUG_PREVENT_CAMERA_SINGULARITIES = false; + + /** + * @type {Dropdown} - DOM element to choose which object to target for the camera + */ + var objectDropdown; + var selectedObjectKey = null; //null; //'closestObject'; + + // polyfill for requestAnimationFrame to provide a smooth update loop + var requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || function(cb) {setTimeout(cb, 17);}; + + // holds the most recent set of objectId/matrix pairs so that they can be rendered on the next frame + // var visibleObjects = {}; + + // hold the current camera position/rotation information, which can be updated with keyboard input + var cameraTranslationMatrix = []; // these are defined here for memory allocation optimization, but updated each time + var cameraRotationMatrix = []; + + var cameraX = 200; //0; + var cameraY = -1170; //0; + var cameraZ = 2230; //7000; + + var cameraVelocityX = 0; + var cameraVelocityY = 0; + var cameraVelocityZ = 0; + + var cameraPitch = 0; + var cameraRoll = 0; + var cameraYaw = 0; + + var cameraVelocityPitch = 0; + var cameraVelocityRoll = 0; + var cameraVelocityYaw = 0; + + // how fast the camera will pan and rotate each frame (units are scaled for vuforia modelview matrix) + var cameraSpeed = 0.0001; // 0.01; + var keyboardSpeedMultiplier = { + translation: 50000, + rotation: 10, + scale: 250000 + }; //1000; + + let scrollWheelMultiplier = 10000; + + var mouseSpeedMultiplier = { + translation: 250, + rotation: .0015, + scale: 350 + }; + + var firstX = 0; + var firstY = 0; + var lastX = 0; + var lastY = 0; + // var firstMouse = true; + var isMouseDown = false; + // var sensitivity = 1; + var unprocessedMouseMovements = []; + + let unprocessedScrollDY = 0; + let unprocessedMouseDX = 0; + let unprocessedMouseDY = 0; + + var mouseMovement = {}; + + var cameraUpVector = [0, 1, 0]; + + var areControlsInverted = true; + + /** + * Enum mapping readable keyboard names to their keyCode + * @type {Readonly<{LEFT: number, UP: number, RIGHT: number, DOWN: number, ONE: number, TWO: number, ESCAPE: number, W: number, A: number, S: number, D: number}>} + */ + var keyCodes = Object.freeze({ + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ONE: 49, + TWO: 50, + ESCAPE: 27, + W: 87, + A: 65, + S: 83, + D: 68, + Q: 81, + E: 69, + SHIFT: 16 + }); + + /** + * For each key included in keyCodes, tracks whether the key is "up" or "down" + * @type {Object.} + */ + var keyStates = {}; + + /** + * for cameraModes.CLOSEST_OBJECT - holds how much the camera has rotated while each objectId was the closest + * @type {Object.} + */ + var rotations = {}; + + var speedMultipliers = { + translation: -100, + rotation: 0.01 + }; + + var destinationCameraMatrix = []; //JSON.parse("[0.9970872369217663,-0.0195192707153495,-0.0737295058877905,0,0,0.9666966751096482,-0.2559248685297132,0,0.076269534990826,0.2551794200218579,0.9638809167265376,0,9.292587708200172e-166,2.2737367544323196e-13,-4789.705780105794,1]"); //[]; + + /** + * Public init method to enable rendering if isDesktop + */ + function initService() { + if (!realityEditor.device.utilities.isDesktop()) { return; } + + // disable right-click context menu so we can use right-click to rotate camera + document.addEventListener('contextmenu', event => event.preventDefault()); + + // setTimeout(function() { + // realityEditor.gui.ar.draw.correctedCameraMatrix = JSON.parse("[0.9970872369217663,-0.0195192707153495,-0.0737295058877905,0,0,0.9666966751096482,-0.2559248685297132,0,0.076269534990826,0.2551794200218579,0.9638809167265376,0,9.292587708200172e-166,2.2737367544323196e-13,-4789.705780105794,1]") + // }, 100); + + try { + addSensitivitySlidersToMenu(); + } catch (e) { + console.warn('Slider components for settings menu not available, skipping', e); + } + + createObjectSelectionDropdown(); + addCameraManipulationListeners(); + update(); + + realityEditor.device.callbackHandler.registerCallback('objectMatrixDiscovered', function(params) { + tryAddingObjectToDropdown(params.objectKey); + }); + + // add a spacebar keyboard listener to toggle visibility of the zone/phone discovery buttons + realityEditor.device.keyboardEvents.registerCallback('keyUpHandler', function(params) { + if (params.event.code === 'KeyV') { + if (objectDropdown) { + if (objectDropdown.dom.style.display !== 'none') { + objectDropdown.dom.style.display = 'none'; + } else { + objectDropdown.dom.style.display = ''; + } + } + } + }); + + if (DEBUG_SHOW_LOGGER) { + closestObjectLog = document.createElement('div'); + closestObjectLog.style.position = 'absolute'; + closestObjectLog.style.left = 0; + closestObjectLog.style.top = 0; + closestObjectLog.style.fontFamily = 'sans-serif'; + closestObjectLog.style.color = 'cyan'; + document.body.appendChild(closestObjectLog); + } + + // setInterval(function() { + // var closestObjectKey = realityEditor.gui.ar.getClosestObject()[0]; + // closestObjectLog.innerText = closestObjectKey; + // }, 1000); + } + + function addSensitivitySlidersToMenu() { + // add sliders for strafe, rotate, and zoom sensitivity + realityEditor.gui.settings.addSlider('Zoom Sensitivity', 'how fast scroll wheel zooms camera', 'cameraZoomSensitivity', '../../../svg/cameraZoom.svg', 0.5, function(newValue) { + console.log('zoom value = ' + newValue); + }); + + realityEditor.gui.settings.addSlider('Pan Sensitivity', 'how fast keybord pans camera', 'cameraPanSensitivity', '../../../svg/cameraPan.svg', 0.5, function(newValue) { + console.log('pan value = ' + newValue); + }); + + realityEditor.gui.settings.addSlider('Rotate Sensitivity', 'how fast right-click dragging rotates camera', 'cameraRotateSensitivity', '../../../svg/cameraRotate.svg', 0.5, function(newValue) { + console.log('rotate value = ' + newValue); + }); + } + + function getCameraZoomSensitivity() { + return Math.max(0.01, realityEditor.gui.settings.toggleStates.cameraZoomSensitivity || 0.5); + } + + function getCameraPanSensitivity() { + return Math.max(0.01, realityEditor.gui.settings.toggleStates.cameraPanSensitivity || 0.5); + } + + function getCameraRotateSensitivity() { + return Math.max(0.01, realityEditor.gui.settings.toggleStates.cameraRotateSensitivity || 0.5); + } + + function createObjectSelectionDropdown() { + if (!objectDropdown) { + + var textStates = { + collapsedUnselected: 'Select Camera Target', + expandedEmpty: 'No Objects Discovered', + expandedOptions: 'Select an Object', + selected: 'Selected: ' + }; + + objectDropdown = new realityEditor.gui.dropdown.Dropdown('objectDropdown', textStates, {width: '400px', left: '310px', top: '30px'}, document.body, true, onObjectSelectionChanged, onObjectExpandedChanged); + + objectDropdown.addSelectable('origin', 'World Origin'); + + // objectDropdown.addSelectable('null', 'No Target'); + // objectDropdown.addSelectable('inverted', 'Inverted'); + // objectDropdown.addSelectable('classic', 'Classic'); + // objectDropdown.addSelectable('closestObject', 'Closest Object'); + // objectDropdown.addSelectable('floatingPoint2000', 'Floating Point (2m)'); + // objectDropdown.addSelectable('floatingPoint5000', 'Floating Point (5m)'); + // objectDropdown.addSelectable('floatingPoint10000', 'Floating Point (10m)'); + // objectDropdown.addSelectable('floatingPoint20000', 'Floating Point (20m)'); + + // objectDropdown.setText('Selected: Inverted', true); + + Object.keys(objects).forEach(function(objectKey) { + tryAddingObjectToDropdown(objectKey); + }); + + // when an object is detected, check if we need to add it to the dropdown + realityEditor.network.addObjectDiscoveredCallback(function(_object, objectKey) { + tryAddingObjectToDropdown(objectKey); + if (objectKey === targetOnLoad) { + setTimeout(function() { + selectObject(objectKey); + }, 500); + } + }); + } + } + + function tryAddingObjectToDropdown(objectKey) { + var alreadyContained = objectDropdown.selectables.map(function(selectableObj) { + return selectableObj.id; + }).indexOf(objectKey) > -1; + + if (!alreadyContained) { + // don't show objects that don't have a valid matrix... todo: add them to menu as soon as a first valid matrix is received + var object = realityEditor.getObject(objectKey); + if (object.matrix && object.matrix.length === 16) { + objectDropdown.addSelectable(objectKey, objectKey); + } + } + } + + function setTargetPositionToObject(objectKey) { + if (objectKey === 'origin') { + cameraTargetPosition = [0, 0, 0]; + isFollowingObjectTarget = true; + return; + } + + var mObj = realityEditor.gui.ar.draw.visibleObjects[objectKey]; + if (mObj) { + var objX = mObj[12] / mObj[15]; + var objY = mObj[13] / mObj[15]; + var objZ = mObj[14] / mObj[15]; + cameraTargetPosition = [objX, objY, objZ]; + isFollowingObjectTarget = true; + } + } + + function onObjectSelectionChanged(selected) { + if (selected && selected.element) { + selectedObjectKey = selected.element.id; + selectObject(selected.element.id); + } else { + selectedObjectKey = null; + } + } + + function selectObject(objectKey) { // todo use this in objectselectionchanged and element clicked + + objectDropdown.setText('Selected: ' + objectKey, true); + + selectedObjectKey = objectKey; + setTargetPositionToObject(objectKey); + previousTargetPosition = [cameraTargetPosition[0], cameraTargetPosition[1], cameraTargetPosition[2]]; + // if (objectKey === 'null' || objectKey === 'inverted') { + // areControlsInverted = objectKey === 'inverted'; + // objectKey = null; + // // resetCamera(); // TODO: instead of reset, calculate the position+rotation needed to maintain visual consistency + // } + } + + function onObjectExpandedChanged(_isExpanded) { + // console.log(isExpanded); + } + + function logMessage(text) { + if (DEBUG_SHOW_LOGGER) { + closestObjectLog.innerText = text; + } + } + + // checks if moving the cameraPosition and cameraTargetPosition by the given velocities would cause the vertical vector to cross a singularity and flip the entire camera view + function wouldCameraFlip(currentVerticalVector, velocity, targetVelocity) { + var nextPosition = add(cameraPosition, velocity); + var nextTargetPosition = add(cameraTargetPosition, targetVelocity); + var ev = nextPosition; + var cv = nextTargetPosition; + var uv = [0, 1, 0]; + var nextForwardVector = normalize(add(ev, negate(cv))); // vector from the camera to the center point + var nextHorizontalVector = normalize(crossProduct(uv, nextForwardVector)); // a "right" vector, orthogonal to n and the lookup vector + var nextVerticalVector = crossProduct(nextForwardVector, nextHorizontalVector); // resulting orthogonal vector to n and u, as the up vector isn't necessarily one anymore + + return ((currentVerticalVector[2] * nextVerticalVector[2]) < 0) && + (Math.abs(currentVerticalVector[0]) + Math.abs(nextVerticalVector[0])) > 1; // flipped from -1 to 1, not passing through 0 + } + + /** + * Main update loop + */ + function update() { + + try { + updateMode_Target(); + } catch (e) { + console.warn('ERROR updating'); + } + + requestAnimationFrame(update); + } + + function updateMode_Target() { + + var cameraVelocity = [0, 0, 0]; + var cameraTargetVelocity = [0, 0, 0]; + + if (isFollowingObjectTarget) { + setTargetPositionToObject(selectedObjectKey); + + // move camera to preserve relative position to target + var movement = add(cameraTargetPosition, negate(previousTargetPosition)); + if (movement[0] !== 0 || movement[1] !== 0 || movement[2] !== 0) { + console.log(movement); + cameraVelocity = add(cameraVelocity, movement); + } + } + + previousTargetPosition = [cameraTargetPosition[0], cameraTargetPosition[1], cameraTargetPosition[2]]; + + // move camera to cameraPosition + // look at cameraTargetPosition + destinationCameraMatrix = lookAt(cameraPosition[0], cameraPosition[1], cameraPosition[2], cameraTargetPosition[0], cameraTargetPosition[1], cameraTargetPosition[2], 0, 1, 0); + + // move the cameraTargetPosition if you strafe + + // move the cameraPosition if you orbit + + var ev = cameraPosition; //[cameraX, cameraY, cameraZ]; + var cv = cameraTargetPosition; //[objX, objY, objZ]; + var uv = [0, 1, 0]; + + // var forwardVector = normalize(add(ev, negate(cv))); // vector from the camera to the center point + // var horizontalVector = normalize(crossProduct(uv, forwardVector)); // a "right" vector, orthogonal to n and the lookup vector + // var verticalVector = crossProduct(forwardVector, horizontalVector); // resulting orthogonal vector to n and u, as the up vector isn't necessarily one anymore + + currentDistanceToTarget = magnitude(add(ev, negate(cv))); + // console.log(distanceToCenter); + // var upRotationVelocity = 0; + + var mCamera = destinationCameraMatrix; // translation is based on what direction you're facing, + var vCamX = normalize([mCamera[0], mCamera[4], mCamera[8]]); + var vCamY = normalize([mCamera[1], mCamera[5], mCamera[9]]); + var vCamZ = normalize([mCamera[2], mCamera[6], mCamera[10]]); + + // var strafeSpeedMultiplier = 25; + // var zoomSpeedMultiplier = 100; + // var threshold = 0.1; + + cameraSpeed = 0.001; // 0.01; + + var forwardVector = normalize(add(ev, negate(cv))); // vector from the camera to the center point + var horizontalVector = normalize(crossProduct(uv, forwardVector)); // a "right" vector, orthogonal to n and the lookup vector + var verticalVector = crossProduct(forwardVector, horizontalVector); // resulting orthogonal vector to n and u, as the up vector isn't necessarily one anymore + + // stop following if you strafe away + function deselectTarget() { + if (isFollowingObjectTarget) { + // objectDropdown.setText('Select Camera Target', true); + objectDropdown.resetSelection(); + } + isFollowingObjectTarget = false; + } + + // update with keyboard + if (!DEBUG_REMOVE_KEYBOARD_CONTROLS) { + + // move the cameraTargetPosition and the cameraPosition equally if you strafe + if (keyStates[keyCodes.LEFT] === 'down') { + let vector = scalarMultiply(horizontalVector, cameraSpeed * keyboardSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + if (keyStates[keyCodes.RIGHT] === 'down') { + let vector = scalarMultiply(negate(horizontalVector), cameraSpeed * keyboardSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + if (keyStates[keyCodes.UP] === 'down') { + let vector = scalarMultiply(verticalVector, cameraSpeed * keyboardSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + if (keyStates[keyCodes.DOWN] === 'down') { + let vector = scalarMultiply(negate(verticalVector), cameraSpeed * keyboardSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + + // move the cameraPosition if you orbit or move in/out + if (keyStates[keyCodes.ONE] === 'down') { // zoom out + let vector = scalarMultiply(forwardVector, cameraSpeed * keyboardSpeedMultiplier.scale); + cameraVelocity = add(cameraVelocity, vector); + } + if (keyStates[keyCodes.TWO] === 'down') { // zoom in (only if you are far enough away) + let vector = scalarMultiply(negate(forwardVector), cameraSpeed * keyboardSpeedMultiplier.scale); + console.log(magnitude(vector), currentDistanceToTarget); + if (magnitude(vector) < currentDistanceToTarget / 3) { // prevent you from zooming beyond it + cameraVelocity = add(cameraVelocity, vector); + } + } + } + + if (keyStates[keyCodes.W] === 'down') { + let vector = scalarMultiply(vCamY, cameraSpeed * keyboardSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + if (keyStates[keyCodes.S] === 'down') { + let vector = scalarMultiply(negate(vCamY), cameraSpeed * keyboardSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + if (keyStates[keyCodes.A] === 'down') { + let vector = scalarMultiply(vCamX, cameraSpeed * keyboardSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + if (keyStates[keyCodes.D] === 'down') { + let vector = scalarMultiply(negate(vCamX), cameraSpeed * keyboardSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + + if (unprocessedScrollDY !== 0) { + + // increase speed as distance increases + let nonLinearFactor = 1.05; // closer to 1 = less intense log (bigger as distance bigger) + let distanceMultiplier = Math.max(1, getBaseLog(nonLinearFactor, currentDistanceToTarget) / 100); + + let vector = scalarMultiply(forwardVector, cameraSpeed * distanceMultiplier * scrollWheelMultiplier * getCameraZoomSensitivity() * unprocessedScrollDY); + + // prevent you from zooming beyond it + let isZoomingIn = unprocessedScrollDY < 0; + if (isZoomingIn && currentDistanceToTarget <= magnitude(vector)) { + // zoom in at most halfway to the origin if you're going to overshoot it + let percentToClipBy = 0.5 * currentDistanceToTarget / magnitude(vector); + vector = scalarMultiply(vector, percentToClipBy); + } + + cameraVelocity = add(cameraVelocity, vector); + + unprocessedScrollDY = 0; + } + + let distancePanFactor = currentDistanceToTarget / 1000; // speed when 1 meter units away, scales up w/ distance + + if (unprocessedMouseDX !== 0) { + // pan if shift held down + if (keyStates[keyCodes.SHIFT] === 'down') { + // STRAFE LEFT-RIGHT + let vector = scalarMultiply(horizontalVector, -1 * distancePanFactor * unprocessedMouseDX * getCameraPanSensitivity()); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } else { + // rotate otherwise + let vector = scalarMultiply(vCamX, -1 * cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDX); + cameraVelocity = add(cameraVelocity, vector); + } + + unprocessedMouseDX = 0; + } + + if (unprocessedMouseDY !== 0) { + // pan if shift held down + if (keyStates[keyCodes.SHIFT] === 'down') { + // STRAFE UP-DOWN + let vector = scalarMultiply(verticalVector, -1 * distancePanFactor * unprocessedMouseDY * getCameraPanSensitivity()); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } else { + // rotate otherwise + let vector = scalarMultiply(vCamY, -1 * cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDY); + cameraVelocity = add(cameraVelocity, vector); + } + + unprocessedMouseDY = 0; + } + + var threshold = 0.1; + + // GO FORWARD-BACKWARD + if (typeof mouseMovement.translation !== 'undefined') { + // zoom in-out + if (Math.abs(mouseMovement.translation.y) >= threshold) { + var forwardSpeed = -mouseMovement.translation.y; + let vector = scalarMultiply(forwardVector, forwardSpeed * mouseSpeedMultiplier.scale); + if (forwardSpeed > 0 || currentDistanceToTarget > 500) { // prevent you from zooming too close to it + cameraVelocity = add(cameraVelocity, vector); + } + } + + // STRAFE LEFT-RIGHT + if (Math.abs(mouseMovement.translation.x) >= threshold) { + var sideSpeed = -mouseMovement.translation.x; + let vector = scalarMultiply(horizontalVector, sideSpeed * mouseSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + + // STRAFE UP-DOWN + if (Math.abs(mouseMovement.translation.z) >= threshold) { + var upSpeed = -mouseMovement.translation.z; + let vector = scalarMultiply(verticalVector, upSpeed * mouseSpeedMultiplier.translation); + cameraTargetVelocity = add(cameraTargetVelocity, vector); + cameraVelocity = add(cameraVelocity, vector); + deselectTarget(); + } + } + + if (typeof mouseMovement.rotation !== 'undefined') { + + // rotate left-right + if (Math.abs(mouseMovement.rotation.z) >= threshold) { + let rotationSpeed = mouseMovement.rotation.z; + let vector = scalarMultiply(vCamX, rotationSpeed * mouseSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + + // rotate up-down + if (Math.abs(mouseMovement.rotation.x) >= threshold) { + let rotationSpeed = mouseMovement.rotation.x; + let vector = scalarMultiply(negate(vCamY), rotationSpeed * mouseSpeedMultiplier.rotation * (2 * Math.PI * currentDistanceToTarget)); + cameraVelocity = add(cameraVelocity, vector); + } + + } + + var shouldMoveCamera = true; + + if (DEBUG_PREVENT_CAMERA_SINGULARITIES) { + var isAboutToFlip = wouldCameraFlip(verticalVector, cameraVelocity, cameraTargetVelocity) || !DEBUG_PREVENT_CAMERA_SINGULARITIES; + shouldMoveCamera = !isAboutToFlip; // prevent the camera from flipping when it's about to cross a singularity + } + + if (shouldMoveCamera) { + // TODO: limit velocity to at most bring you to +epsilon distance to target, instead of flipping world + // when you go through it / beyond it + cameraPosition = add(cameraPosition, cameraVelocity); + cameraTargetPosition = add(cameraTargetPosition, cameraTargetVelocity); + } + + logMessage(prettyPrint(cameraPosition, 0) + ' -> ' + prettyPrint(cameraTargetPosition, 0) + ' (' + + currentDistanceToTarget.toFixed(0) + ') ' + (isFollowingObjectTarget ? '(Following)' : '()')); + + // tween the matrix every frame to animate it to the new position + realityEditor.gui.ar.draw.correctedCameraMatrix = tweenMatrix(realityEditor.gui.ar.draw.correctedCameraMatrix, destinationCameraMatrix, 0.3); + + var rotatedGroundPlaneMatrix = []; + //var rotation3d = makeRotationY(Math.PI/2); + realityEditor.gui.ar.utilities.multiplyMatrix(window.gpMat, realityEditor.gui.ar.draw.correctedCameraMatrix, rotatedGroundPlaneMatrix); + + realityEditor.gui.ar.draw.groundPlaneMatrix = rotatedGroundPlaneMatrix; + } + + function getBaseLog(x, y) { + return Math.log(y) / Math.log(x); + } + + function prettyPrint(matrix, precision) { + return '[ ' + matrix[0].toFixed(precision) + ', ' + matrix[1].toFixed(precision) + ', ' + matrix[2].toFixed(precision) + ']'; + } + + function tweenMatrix(currentMatrix, destination, tweenSpeed) { + if (typeof tweenSpeed === 'undefined') { tweenSpeed = 0.5; } // default value + + if (currentMatrix.length !== destination.length) { + console.warn('matrices are inequal lengths. cannot be tweened so just assigning current=destination'); + return realityEditor.gui.ar.utilities.copyMatrix(destination); + } + if (tweenSpeed <= 0 || tweenSpeed >= 1) { + console.warn('tween speed should be between 0 and 1. cannot be tweened so just assigning current=destination'); + return realityEditor.gui.ar.utilities.copyMatrix(destination); + } + + var m = []; + for (var i = 0; i < currentMatrix.length; i++) { + m[i] = destination[i] * tweenSpeed + currentMatrix[i] * (1.0 - tweenSpeed); + } + return m; + } + + function getTargetPosition(targetObjectKey) { + if (targetObjectKey === 'floatingPoint2000' || targetObjectKey === 'floatingPoint5000' || targetObjectKey === 'floatingPoint10000' || targetObjectKey === 'floatingPoint20000') { + // figure out camera position and forward vector. choose a point distance D along the forward vector. + var floatingPointDistance = parseInt(targetObjectKey.split('floatingPoint')[1]); // pulls out 2000, 5000, or 10000 from targetObjectKey + var mCamera = destinationCameraMatrix; + var vCamZ = normalize([mCamera[2], mCamera[6], mCamera[10]]); // this is the forward vector + var relativePos = scalarMultiply(vCamZ, floatingPointDistance); + var targetPos = add([cameraX, cameraY, cameraZ], negate(relativePos)); // not sure why it has to be negated, but flips camera otherwise + return { + x: targetPos[0], + y: targetPos[1], + z: targetPos[2] + }; + } + + var mObj = realityEditor.gui.ar.draw.visibleObjects[targetObjectKey]; + var objX = mObj[12] / mObj[15]; + var objY = mObj[13] / mObj[15]; + var objZ = mObj[14] / mObj[15]; + + return { + x: objX, + y: objY, + z: objZ + }; + } + + function multiplyMatrixVector(M, v) { + return [M[0] * v[0] + M[1] * v[1] + M[2] * v[2], + M[3] * v[0] + M[4] * v[1] + M[5] * v[2], + M[6] * v[0] + M[7] * v[1] + M[8] * v[2]]; + } + + /** + * Adds keyboard listeners and initializes the camera matrix + */ + function addCameraManipulationListeners() { + + console.log('add desktop camera keyboard, mouse, and 3d mouse controls'); + + cameraTranslationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + destinationCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + + // set up the keyStates map with default value of "up" for each key + Object.keys(keyCodes).forEach(function(keyName) { + keyStates[keyCodes[keyName]] = 'up'; + }); + + // when a key is pressed down, automatically update that entry in keyStates + // also freeze/pause if you pressed a relevant arrow key + document.addEventListener('keydown', function(event) { + event.preventDefault(); + var code = event.keyCode ? event.keyCode : event.which; + if (keyStates.hasOwnProperty(code)) { + keyStates[code] = 'down'; + } + }); + + // when a key is released, automatically update that entry in keyStates + // also unfreeze/un-pause when the escape key is pressed + document.addEventListener('keyup', function(event) { + event.preventDefault(); + var code = event.keyCode ? event.keyCode : event.which; + if (keyStates.hasOwnProperty(code)) { + keyStates[code] = 'up'; + + // reset when escape pressed + if (code === keyCodes.ESCAPE) { + resetCamera(); + } + } + }); + + var prevScrollTop = 0; + var direction = 'neutral'; + document.addEventListener('scroll', function(event) { + var newScrollTop = event.currentTarget.scrollingElement.scrollTop; + + if (direction !== 'up' && newScrollTop < 0) { + direction = 'up'; + } else if (direction !== 'down' && newScrollTop > 0) { + direction = 'down'; + } + + if (newScrollTop > prevScrollTop && direction === 'down') { + cameraVelocityZ -= cameraSpeed * keyboardSpeedMultiplier.scale; + } else if (newScrollTop < prevScrollTop && direction === 'up') { + cameraVelocityZ += cameraSpeed * keyboardSpeedMultiplier.scale; + } + + prevScrollTop = newScrollTop; + event.preventDefault(); + }); + + window.addEventListener("wheel", function(event) { + let scrollAmount = event.deltaY; + unprocessedScrollDY += scrollAmount; + event.preventDefault(); + }, {passive: false}); // in order to call preventDefault, wheel needs to be active not passive + + document.addEventListener('pointerdown', function(event) { + + if (event.button === 2) { // right click, 0 is left, 1 is middle button + console.log('right click 2'); + + isMouseDown = true; + firstX = event.pageX; + firstY = event.pageY; + lastX = event.pageX; + lastY = event.pageY; + + unprocessedMouseMovements.push({ + x: event.pageX - firstX, + y: event.pageY - firstY + }); + } + + if (keyStates[keyCodes.SHIFT] !== 'down') { return; } + + console.log(event.currentTarget); + + var overlappingDivs = realityEditor.device.utilities.getAllDivsUnderCoordinate(event.pageX, event.pageY); + + var firstVisibleFrame = null; + overlappingDivs.forEach(function(elt) { + if (firstVisibleFrame) { return; } + + if (elt.classList.contains('visibleFrame')) { + firstVisibleFrame = elt; + } + }); + + if (firstVisibleFrame) { + var objectKey = firstVisibleFrame.getAttribute('data-object-key'); + console.log(objectKey); + selectObject(objectKey); + } + + // overlappingDivs.filter(function(elt) { + // return (typeof elt.parentNode.dataset.displayAfterTouch !== 'undefined'); + // }).forEach(function(elt) { + // elt.parentNode.style.display = 'none'; // TODO: instead of changing display, maybe just change pointerevents css to none + // }); + + }); + + document.addEventListener('pointerup', function(event) { + if (event.button !== 2) { + return; + } + + isMouseDown = false; + unprocessedMouseMovements = []; + lastX = 0; + lastY = 0; + }); + + document.addEventListener('pointermove', function(event) { + if (isMouseDown) { + + var xOffset = event.pageX - lastX; + var yOffset = event.pageY - lastY; + + unprocessedMouseDX += xOffset; + unprocessedMouseDY += yOffset; + + lastX = event.pageX; + lastY = event.pageY; + + unprocessedMouseMovements.push({ + x: event.pageX - firstX, + y: event.pageY - firstY + }); + } + }); + + realityEditor.network.realtime.addDesktopSocketMessageListener('/mouse/transformation', function(msgContent) { + // console.log('received 3d mouse data', msgContent); + mouseMovement = msgContent; + }); + } + + function resetCamera() { + rotations = {}; + cameraX = 0; //-500; //-1500; //0; + cameraY = 0; //-11673; //7639; //0; + cameraZ = -7000; //13307; //-12993; //5000; + cameraVelocityX = 0; + cameraVelocityY = 0; + cameraVelocityZ = 0; + cameraPitch = 0; + cameraRoll = 0; + cameraYaw = 0; + cameraTranslationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + cameraRotationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + // cameraUpVector = [0.058374143427579205, -0.9982947757947471, 0]; + // destinationCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); + + // realityEditor.gui.ar.draw.correctedCameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 600, 200, -4750, 1]; + // destinationCameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 600, 200, -4750, 1]; + + // realityEditor.gui.ar.draw.correctedCameraMatrix = [-0.9979047869606354,0.04357977057008431,-0.04782091339682644,0,0,0.7391224253723763,0.673571110063114,0,0.06469958393257225,0.6721598350903706,-0.7375738064290496,0,-7.3603317075507955e-62,1.8189894035458557e-12,-13467.956791262939,1]; + // destinationCameraMatrix = [-0.9979047869606354,0.04357977057008431,-0.04782091339682644,0,0,0.7391224253723763,0.673571110063114,0,0.06469958393257225,0.6721598350903706,-0.7375738064290496,0,-7.3603317075507955e-62,1.8189894035458557e-12,-13467.956791262939,1]; + + } + + // Working look-at matrix generator (with a set of vector3 math functions) + function lookAt( eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ ) { + var ev = [eyeX, eyeY, eyeZ]; + var cv = [centerX, centerY, centerZ]; + var uv = [upX, upY, upZ]; + + var n = normalize(add(ev, negate(cv))); // vector from the camera to the center point + var u = normalize(crossProduct(uv, n)); // a "right" vector, orthogonal to n and the lookup vector + var v = crossProduct(n, u); // resulting orthogonal vector to n and u, as the up vector isn't necessarily one anymore + + return [u[0], v[0], n[0], 0, + u[1], v[1], n[1], 0, + u[2], v[2], n[2], 0, + dotProduct(negate(u), ev), dotProduct(negate(v), ev), dotProduct(negate(n), ev), 1]; + } + + function scalarMultiply(A, x) { + return [A[0] * x, A[1] * x, A[2] * x]; + } + + function negate(A) { + return [-A[0], -A[1], -A[2]]; + } + + function add(A, B) { + return [A[0] + B[0], A[1] + B[1], A[2] + B[2]]; + } + + function magnitude(A) { + return Math.sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]); + } + + function normalize(A) { + var mag = magnitude(A); + return [A[0] / mag, A[1] / mag, A[2] / mag]; + } + + function crossProduct(A, B) { + var a = A[1] * B[2] - A[2] * B[1]; + var b = A[2] * B[0] - A[0] * B[2]; + var c = A[0] * B[1] - A[1] * B[0]; + return [a, b, c]; + } + + function dotProduct(A, B) { + return A[0] * B[0] + A[1] * B[1] + A[2] * B[2]; + } + + realityEditor.addons.addCallback('init', initService); +})(); + + var gp_makeRotationX = function ( theta ) { + var c = Math.cos( theta ), s = Math.sin( theta ); + return [1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1]; + }; + + var gp_makeRotationY = function ( theta ) { + var c = Math.cos( theta ), s = Math.sin( theta ); + return [c, 0, s, 0, + 0, 1, 0, 0, + -s, 0, c, 0, + 0, 0, 0, 1]; + }; + + var gp_makeRotationZ = function ( theta ) { + var c = Math.cos( theta ), s = Math.sin( theta ); + return [c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]; + }; + + window.gpMat = gp_makeRotationY(0); \ No newline at end of file diff --git a/content_scripts/desktopRenderer.js b/content_scripts/desktopRenderer.js new file mode 100644 index 00000000..e0500634 --- /dev/null +++ b/content_scripts/desktopRenderer.js @@ -0,0 +1,310 @@ +createNameSpace('realityEditor.gui.ar.desktopRenderer'); + +/** + * @fileOverview realityEditor.device.desktopRenderer.js + * For remote desktop operation: renders background graphics simulating the context streamed from a connected phone. + * e.g. a point or plane for each marker, or an entire point cloud of the background contents + */ + +(function(exports) { + + var TEMP_DISABLE_MARKER_PLANES = true; + + var visibleObjectsCopy = {}; + var elementsAdded = []; + + var utilities = realityEditor.gui.ar.utilities; + var tempResMatrix = []; + var activeObjectMatrix = []; + + /** + * @type {Canvas} - the DOM element where the images streamed from a reality zone are rendered + */ + var backgroundCanvas; + /** + * @type {Canvas} + * Scratch space to draw and chroma-key the image from the RZ which is + * drawing the point cloud and background + */ + var primaryBackgroundCanvas; + // Whether the primary canvas is ready for use in bg rendering + var primaryDrawn = false; + + /** + * @type {Canvas} + * Scratch space to draw and chroma-key the image from the RZ which is + * drawing only its point cloud + */ + var secondaryBackgroundCanvas; + // Whether the secondary canvas is ready for use in bg rendering + var secondaryDrawn = false; + + var ONLY_REQUIRE_PRIMARY = true; + + /** + * Public init method to enable rendering if isDesktop + */ + function initService() { + if (!realityEditor.device.utilities.isDesktop()) { return; } + + if (!TEMP_DISABLE_MARKER_PLANES) { + + // registers a callback to the gui.ar.draw.update loop so that this module can manage its own rendering + realityEditor.gui.ar.draw.addUpdateListener(function(visibleObjects) { + + // remove old plane elements that have disappeared + for (var objectKey in visibleObjectsCopy) { + if (!visibleObjectsCopy.hasOwnProperty(objectKey)) continue; + if (!visibleObjects.hasOwnProperty(objectKey)) { + removePlaneElement(objectKey); + } + } + + // cache the most recent visible objects so we can detect when one disappears + visibleObjectsCopy = visibleObjects; + + for (objectKey in visibleObjects) { + if (!visibleObjects.hasOwnProperty(objectKey)) continue; + if (!objects.hasOwnProperty(objectKey)) continue; + + var object = realityEditor.getObject(objectKey); + if (object.isWorldObject) continue; + if (object.hasOwnProperty('targetType') && object.targetType === 'model') continue; + + renderMarkerPlane(objectKey, visibleObjects[objectKey]); + } + + }); + + } + + // create background canvas and supporting canvasses + + backgroundCanvas = document.createElement('canvas'); + backgroundCanvas.id = 'desktopBackgroundRenderer'; + backgroundCanvas.classList.add('desktopBackgroundRenderer'); + backgroundCanvas.style.transform = 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -1, 1)'; + backgroundCanvas.style.transformOrigin = 'top left'; + backgroundCanvas.style.position = 'absolute'; + primaryBackgroundCanvas = document.createElement('canvas'); + secondaryBackgroundCanvas = document.createElement('canvas'); + + updateCanvasSize(); + window.addEventListener('resize', updateCanvasSize); + + // backgroundRenderer.src = "https://www.youtube.com/embed/XOacA3RYrXk?enablejsapi=1&rel=0&controls=0&playsinline=1&vq=large"; + + // add the Reality Zone background behind everything else + document.body.insertBefore(backgroundCanvas, document.body.childNodes[0]); + + // if (typeof msgContent.sendToBackground !== "undefined") { + // + // var iframe = globalDOMCache['iframe' + tempThisObject.uuid]; + // var src = iframe.src; + // + // var desktopBackgroundRenderer = document.getElementById('desktopBackgroundRenderer'); + // if (desktopBackgroundRenderer) { + // if (desktopBackgroundRenderer.src !== src) { + // desktopBackgroundRenderer.src = src; + // } + // } + // + // if (iframe) { + // iframe.style.display = 'none'; + // } + // + // var div = globalDOMCache[tempThisObject.uuid]; //globalDOMCache['object' + tempThisObject.uuid]; + // if (div) { + // // div.style.pointerEvents = 'none'; + // globalDOMCache[tempThisObject.uuid].style.display = 'none'; + // } + // + // } + } + + /** + * Updates canvas size for resize events + */ + function updateCanvasSize() { + backgroundCanvas.width = window.innerWidth; + backgroundCanvas.height = window.innerHeight; + primaryBackgroundCanvas.width = window.innerWidth; + primaryBackgroundCanvas.height = window.innerHeight; + secondaryBackgroundCanvas.width = window.innerWidth; + secondaryBackgroundCanvas.height = window.innerHeight; + primaryDrawn = false; + secondaryDrawn = false; + } + + /** + * Takes a message containing an encoded image, and chroma keys it for use as the fullscreen background on the desktop + * @param {string} source - either primary or secondary + * @param {string} msgContent - contains the image data encoded as a base64 string + */ + function processImageFromSource(source, msgContent) { + // if (typeof msgContent.base64String !== 'undefined') { + // var imageBlobUrl = realityEditor.device.utilities.decodeBase64JpgToBlobUrl(msgContent.base64String); + // backgroundRenderer.src = imageBlobUrl; + // } + let parts = msgContent.split(';_;'); + let rgbImage = parts[0]; + let alphaImage = parts[1]; + let editorId = parts[2]; + let rescaleFactor = parts[3]; + + if (editorId !== globalStates.tempUuid) { + // console.log('ignoring image from other editorId'); + return; + } + + let prom; + if (source === 'primary') { + prom = renderImageAndChromaKey(primaryBackgroundCanvas, rgbImage, alphaImage).then(function() { + primaryDrawn = true; + }); + } else if (source === 'secondary') { + prom = renderImageAndChromaKey(secondaryBackgroundCanvas, rgbImage, alphaImage).then(function() { + secondaryDrawn = true; + }); + } + if (!prom) { + return; + } + prom.then(function() { + if (primaryDrawn && (secondaryDrawn || ONLY_REQUIRE_PRIMARY)) { + renderBackground(); + backgroundCanvas.style.transform = 'matrix3d(' + rescaleFactor + ', 0, 0, 0, 0, ' + rescaleFactor + ', 0, 0, 0, 0, 1, 0, 0, 0, -1, 1)'; + } + }); + } + + function renderBackground() { + let gfx = backgroundCanvas.getContext('2d'); + gfx.clearRect(0, 0, backgroundCanvas.width, backgroundCanvas.height); + gfx.drawImage(primaryBackgroundCanvas, 0, 0); + gfx.drawImage(secondaryBackgroundCanvas, 0, 0); + realityEditor.device.desktopStats.imageRendered(); + } + + function loadImage(width, height, imageStr) { + if (!imageStr) { + return Promise.resolve(null); + } + return new Promise(function(res) { + let img = new Image(width, height); + img.onload = function() { + img.onload = null; + res(img); + }; + img.src = imageStr; + }); + } + + function renderImageAndChromaKey(canvas, rgbImageStr, alphaImageStr) { + return Promise.all([ + loadImage(canvas.width, canvas.height, rgbImageStr), + loadImage(canvas.width, canvas.height, alphaImageStr), + ]).then(function([rgbImage, alphaImage]) { + let gfx = canvas.getContext('2d'); + + if (!alphaImage) { + gfx.drawImage(rgbImage, 0, 0); + return; + } + + gfx.drawImage(alphaImage, 0, 0); + let alphaId = gfx.getImageData(0, 0, canvas.width, canvas.height); + gfx.drawImage(rgbImage, 0, 0); + let id = gfx.getImageData(0, 0, canvas.width, canvas.height); + let nPixels = canvas.width * canvas.height; + for (let i = 0; i < nPixels; i++) { + id.data[4 * i + 3] = alphaId.data[4 * i + 0]; + } + gfx.putImageData(id, 0, 0); + }); + } + + function renderMarkerPlane(objectKey, visibleObjectMatrix) { + // var object = realityEditor.getObject(objectKey); + + // create div for ghost if needed + if (!globalDOMCache['plane' + objectKey]) { + createPlaneElement(objectKey); + } else { + if (globalDOMCache['plane' + objectKey].style.display === 'none') { + globalDOMCache['plane' + objectKey].style.display = 'inline'; + } + } + + utilities.multiplyMatrix(visibleObjectMatrix, globalStates.projectionMatrix, activeObjectMatrix); + + var finalMatrix = activeObjectMatrix; + + // adjust Z-index so it gets rendered behind all the real frames/nodes + // calculate center Z of frame to know if it is mostly in front or behind the marker plane + var projectedPoint = realityEditor.gui.ar.utilities.multiplyMatrix4([0, 0, 0, 1], activeObjectMatrix); + finalMatrix[14] = -5 + 1000000 / Math.max(10, projectedPoint[2]); // (don't add extra 200) so it goes behind real + + if (globalStates.guiState !== 'ui') { + finalMatrix[14] = 100; + } + + // actually adjust the CSS to draw it with the correct transformation + globalDOMCache['plane' + objectKey].style.transform = 'matrix3d(' + finalMatrix.toString() + ')'; // TODO: simplify to something meaningful + + // // store the screenX and screenY within the ghost to help us later draw lines to the ghosts + // var ghostCenterPosition = getDomElementCenterPosition(globalDOMCache['ghost' + activeKey]); + // ghostVehicle.screenX = ghostCenterPosition.x; + // ghostVehicle.screenY = ghostCenterPosition.y; + + } + + /** + * Creates a dotted-outline DOM element for the given frame or node, using its width and height. + * Styles it differently (red) if the reason for the ghost is that the frame/node was deleted. + * Also add it to the elementsAdded list, to keep track of which ghosts are in existence. + * @param {string} objectKey + */ + function createPlaneElement(objectKey) { + var object = realityEditor.getObject(objectKey); + + var planeDiv = document.createElement('div'); + planeDiv.id = 'plane' + objectKey; + planeDiv.classList.add('main', 'ignorePointerEvents', 'visibleFrameContainer'); + + planeDiv.style.width = globalStates.height + 'px'; + planeDiv.style.height = globalStates.width + 'px'; + planeDiv.style.left = 0; + planeDiv.style.top = 0; + + var innerPlane = document.createElement('img'); + innerPlane.classList.add('markerPlaneElement'); + var innerWidth = object.targetSize.width * 1000; + var innerHeight = object.targetSize.height * 1000; + innerPlane.style.width = innerWidth + 'px'; + innerPlane.style.height = innerHeight + 'px'; + innerPlane.style.left = (globalStates.height - innerWidth) / 2 + 'px'; + innerPlane.style.top = (globalStates.width - innerHeight) / 2 + 'px'; + + var objectName = objectKey.slice(0, -12); // get objectName from objectId + innerPlane.src = 'http://' + object.ip + ':' + httpPort + '/obj/' + objectName + '/target/target.jpg'; + + planeDiv.appendChild(innerPlane); + + document.getElementById('GUI').appendChild(planeDiv); + globalDOMCache['plane' + objectKey] = planeDiv; + + // maintain an elementsAdded list so that we can remove them all on demand + elementsAdded.push(objectKey); + } + + function removePlaneElement(objectKey) { + if (globalDOMCache['plane' + objectKey]) { + globalDOMCache['plane' + objectKey].style.display = 'none'; + } + } + + exports.processImageFromSource = processImageFromSource; + + realityEditor.addons.addCallback('init', initService); +})(realityEditor.gui.ar.desktopRenderer); diff --git a/content_scripts/desktopStats.js b/content_scripts/desktopStats.js new file mode 100644 index 00000000..8b6fb935 --- /dev/null +++ b/content_scripts/desktopStats.js @@ -0,0 +1,100 @@ +createNameSpace('realityEditor.device.desktopStats'); + +/** + * @fileOverview realityEditor.device.desktopRenderer.js + * For remote desktop operation: renders background graphics simulating the context streamed from a connected phone. + * e.g. a point or plane for each marker, or an entire point cloud of the background contents + */ + +(function(exports) { + + let stats = new Stats(); + + let imagesPerSecond = 0; + let numImages = 0; + let imageStartTime = null; + let currentImageTime = null; + let imagesPerSecondElement = null; + + let isVisible = false; + + function initService() { + document.body.appendChild(stats.dom); + + imagesPerSecondElement = document.createElement('div'); + imagesPerSecondElement.style.color = 'white'; + imagesPerSecondElement.style.fontSize = '30px'; + imagesPerSecondElement.style.position = 'absolute'; + imagesPerSecondElement.style.left = '100px'; + imagesPerSecondElement.style.top = '0'; + document.body.appendChild(imagesPerSecondElement); + + isVisible = true; + + update(); // start update loop + } + + function update() { + if (!isVisible) { + return; + } + + stats.update(); + requestAnimationFrame(update); + + if (imageStartTime !== null) { + updateImagesPerSecond(); + } + } + + function startImageTimer() { + imageStartTime = (new Date()).getTime(); + } + + function resetImageTimer() { + numImages = 0; + imageStartTime = null; + currentImageTime = null; + } + + function imageRendered() { + if (!isVisible) { + return; + } + + if (currentImageTime > 10000) { + resetImageTimer(); // reset every 10 seconds to maintain accurate temporal averages + } + if (imageStartTime === null) { + startImageTimer(); + } + numImages += 1; + } + + function updateImagesPerSecond() { + currentImageTime = (new Date()).getTime() - imageStartTime; + imagesPerSecond = numImages / (currentImageTime/1000); + imagesPerSecondElement.innerText = imagesPerSecond.toFixed(2); + } + + function show() { + stats.dom.style.visibility = 'visible'; + imagesPerSecondElement.style.visibility = 'visible'; + isVisible = true; + resetImageTimer(); + update(); + } + + function hide() { + stats.dom.style.visibility = 'hidden'; + imagesPerSecondElement.style.visibility = 'hidden'; + isVisible = false; + } + + exports.imageRendered = imageRendered; + exports.resetImageTimer = resetImageTimer; + exports.show = show; + exports.hide = hide; + + realityEditor.addons.addCallback('init', initService); +})(realityEditor.device.desktopStats); diff --git a/interfaces/.idea/inspectionProfiles/profiles_settings.xml b/interfaces/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..0eefe328 --- /dev/null +++ b/interfaces/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/interfaces/.idea/interfaces.iml b/interfaces/.idea/interfaces.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/interfaces/.idea/interfaces.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/interfaces/.idea/misc.xml b/interfaces/.idea/misc.xml new file mode 100644 index 00000000..4dab1693 --- /dev/null +++ b/interfaces/.idea/misc.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interfaces/.idea/modules.xml b/interfaces/.idea/modules.xml new file mode 100644 index 00000000..2d64fe0e --- /dev/null +++ b/interfaces/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/interfaces/.idea/vcs.xml b/interfaces/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/interfaces/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/interfaces/.idea/workspace.xml b/interfaces/.idea/workspace.xml new file mode 100644 index 00000000..200614c3 --- /dev/null +++ b/interfaces/.idea/workspace.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + 1602597122892 + + + + + + + + + \ No newline at end of file diff --git a/interfaces/desktopEditor/config.html b/interfaces/desktopEditor/config.html new file mode 100644 index 00000000..45ef0501 --- /dev/null +++ b/interfaces/desktopEditor/config.html @@ -0,0 +1,387 @@ + + + + + Remote Operator Configurator + + + + + + + +
+ The desktopEditor interface allows you to host a Remote Operator userinterface on localhost:8081. Just configure + the absolute path to the vuforia-spatial-toolbox-userinterface directory and restart your server. +
+ +
+ +
+ + + + + diff --git a/interfaces/desktopEditor/index.js b/interfaces/desktopEditor/index.js new file mode 100644 index 00000000..6467087d --- /dev/null +++ b/interfaces/desktopEditor/index.js @@ -0,0 +1,256 @@ +/** + * Created by Ben Reynolds on 10/01/18. + */ + +/** + * Set to true to enable the hardware interface + **/ + +const os = require('os'); +const path = require('path'); + +const server = require('@libraries/hardwareInterfaces'); +const utilities = require('@libraries/utilities'); +const Addons = require('@libraries/addons/Addons'); +const LocalUIApp = require('@libraries/LocalUIApp'); + +const settings = server.loadHardwareInterface(__dirname); + +exports.enabled = settings('enabled'); +exports.configurable = true; // can be turned on/off/adjusted from the web frontend + +/** + * These settings will be exposed to the webFrontend to potentially be modified + */ +exports.settings = { + userinterfacePath: { + value: settings('userinterfacePath'), + type: 'text', + helpText: 'The absolute path to the vuforia-spatial-toolbox-userinterface.' + } +}; + +if (exports.enabled) { + + const addonPaths = [ + path.join(__dirname, '../../../'), + path.join(os.homedir(), 'Documents/toolbox/addons'), + ]; + + const addons = new Addons(addonPaths); + const addonFolders = addons.listAddonFolders(); + + console.log('addonFolders', addonFolders); + + // Set this in Documents/realityobjects/.identity/desktopEditor/settings.json. Give it contents like: + /** + { + 'enabled': 'true', + 'userinterfacePath': '/Users/Benjamin/Code/github-ptc/of_v0.9.8_ios/apps/myApps/realityeditor-ios/bin/data/userinterface' + } + */ + const userinterfacePath = settings('userinterfacePath'); + + // userinterfacePath = 'F:/RealityBeast/UI'; + + try { + const localUIApp = new LocalUIApp(userinterfacePath, addonFolders); + localUIApp.setup(); + + console.log('SUCCESSFULLY CREATED LOCAL_UI_APP'); + + // add the middleware + // use the CORS cross origin REST model + startHTTPServer(localUIApp, 8081); + } catch (e) { + console.warn('CANNOT START DESKTOP EDITOR ON PORT 8081: ', e); + } + +} + +function startHTTPServer(localUIApp, port) { + let mouseTranslation = null; + let mouseRotation = null; + let mouseConnected = false; + + const http = require('http').Server(localUIApp.app); + const io = require('socket.io')(http); + + // httpServers[port] = http; + // ioSockets[port] = io; + + http.listen(port, function() { + console.log('~~~ started desktop reality editor on port ' + port); + + server.subscribeToMatrixStream(function(visibleObjects) { + // console.log('desktop editor is viewing ' + Object.keys(visibleObjects).length + ' objects'); + io.emit('visibleObjects', visibleObjects); + }); + + server.subscribeToUDPMessages(function(msgContent) { + io.emit('udpMessage', msgContent); + }); + + function socketServer() { + + io.on('connection', function (socket) { + + console.log('connected to socket', socket.id); + + socket.on('/subscribe/editorUpdates', function (msg) { + + var msgContent = JSON.parse(msg); + console.log('/subscribe/editorUpdates', msgContent); + + // realityEditorSocketArray[socket.id] = {object: msgContent.object, protocol: thisProtocol}; + + // io.sockets.connected[socket.id].emit('object', JSON.stringify({ + // object: msgContent.object, + // frame: msgContent.frame, + // node: key, + // data: objects[msgContent.object].frames[msgContent.frame].nodes[key].data + // })); + + // io.sockets.connected[socket.id].emit('object/publicData', JSON.stringify({ + // object: msgContent.object, + // frame: msgContent.frame, + // publicData: publicData + // })); + + }); + + socket.on('/matrix/visibleObjects', function (msg) { + + var msgContent = JSON.parse(msg); + // console.log(msgContent); + + io.emit('/matrix/visibleObjects', msgContent); + }); + + socket.on('/update', function(msg) { + var objectKey; + var frameKey; + var nodeKey; + + var msgContent = JSON.parse(msg); + if (typeof msgContent.objectKey !== 'undefined') { + objectKey = msgContent.objectKey; + } + if (typeof msgContent.frameKey !== 'undefined') { + frameKey = msgContent.frameKey; + } + if (typeof msgContent.nodeKey !== 'undefined') { + nodeKey = msgContent.nodeKey; + } + + if (objectKey && frameKey && nodeKey) { + io.emit('/update/node', msgContent); + } else if (objectKey && frameKey) { + io.emit('/update/frame', msgContent); + } else if (objectKey) { + io.emit('/update/object', msgContent); + } + + }); + + /** + * Implements the native API functionality of UDP sending for the hosted reality editor desktop app + */ + socket.on('/nativeAPI/sendUDPMessage', function(msg) { + var msgContent = JSON.parse(msg); + utilities.actionSender(msgContent); + }); + + var callibrationFrames = 100; + + try { + connectTo6DMouse(); + } catch (e) { + console.log('Did not connect to input hardware. Control remote operator with mouse' + + ' scroll whell, right-click drag and shift-right-click-drag'); + } + function connectTo6DMouse() { + if (!mouseConnected) { + mouseConnected = true; + var sm = require('../6DMouse/3DConnexion.js'); + var callibration = null; + sm.spaceMice.onData = function(mouse) { + // translation + // console.log('desktop editor translate', JSON.stringify(mouse.mice[0]['translate'])); + // rotation + // console.log('desktop editor rotate', JSON.stringify(mouse.mice[0]['rotate'])); + mouseTranslation = mouse.mice[0]['translate']; + mouseRotation = mouse.mice[0]['rotate']; + + if (!callibration) { + callibrationFrames--; + if (callibrationFrames === 0) { + + if (mouseTranslation.x < 1.0 && mouseTranslation.x > -1.0) mouseTranslation.x = 0; + if (mouseTranslation.y < 1.0 && mouseTranslation.y > -1.0) mouseTranslation.y = 0; + if (mouseTranslation.z < 1.0 && mouseTranslation.z > -1.0) mouseTranslation.z = 0; + + mouseTranslation.x *= 20; + mouseTranslation.y *= 20; + mouseTranslation.z *= 20; + + callibration = { + + x: mouseTranslation.x * 20, + y: mouseTranslation.y * 20, + z: mouseTranslation.z * 20 + }; + console.log('callibrated mouse at ', callibration); + } + } else { + + // var threshold = 0.1; + // if (Math.abs(mouseTranslation.x) < threshold) { + // mouseTranslation.x = 0; + // } + // if (Math.abs(mouseTranslation.y) < threshold) { + // mouseTranslation.y = 0; + // } + // if (Math.abs(mouseTranslation.z) < threshold) { + // mouseTranslation.z = 0; + // } + // + // if (Math.abs(mouseRotation.x) < threshold) { + // mouseRotation.x = 0; + // } + // if (Math.abs(mouseRotation.y) < threshold) { + // mouseRotation.y = 0; + // } + // if (Math.abs(mouseRotation.z) < threshold) { + // mouseRotation.z = 0; + // } + + // mouseTranslation.z += 0.3; + + // console.log({ + // x: mouseTranslation.x - callibration.x, + // y: mouseTranslation.y - callibration.y, + // z: mouseTranslation.z - callibration.z + // }); + + io.emit('/mouse/transformation', { + translation: { + x: mouseTranslation.x - callibration.x, + y: mouseTranslation.y - callibration.y, + z: mouseTranslation.z - callibration.z + }, + rotation: mouseRotation + }); + + } + }; + } + } + + }); + } + + socketServer(); + }); +} + diff --git a/interfaces/desktopEditor/public/hololens.html b/interfaces/desktopEditor/public/hololens.html new file mode 100644 index 00000000..4611b887 --- /dev/null +++ b/interfaces/desktopEditor/public/hololens.html @@ -0,0 +1,85 @@ + + + + + + HoloLens Editor + + + + + + + + + + + + diff --git a/interfaces/pass/index.js b/interfaces/pass/index.js new file mode 100644 index 00000000..4958923a --- /dev/null +++ b/interfaces/pass/index.js @@ -0,0 +1,689 @@ +var server = require('@libraries/hardwareInterfaces'); +var utilities = require('@libraries/utilities'); + +var settings = server.loadHardwareInterface(__dirname); + +exports.enabled = settings('enabled'); +exports.configurable = true; // can be turned on/off/adjusted from the web frontend + +if (exports.enabled) { + var express = require('express'); + var app = require('express')(); + var cors = require('cors'); // Library for HTTP Cross-Origin-Resource-Sharing + app.use(cors()); + // allow requests from all origins with '*'. TODO make it dependent on the local network. this is important for security + app.options('*', cors()); + + app.use(express.static(__dirname + '/public')); + var http = require('http').Server(app); + var fs = require('fs'); + var ip = require("ip"); + var glob = require("glob") + var io = require('socket.io')(http, { wsEngine: 'ws' }); + + var socket_list = []; + var type_list = []; + + + var stations = []; + var viewers = []; + var editorToSocketId = {}; + var socketToEditorId = {}; + var viewer_width = null; + var viewer_height = null; + + //custom vuforia server: + var vuforiaResultClient = null; + var vuforiaCamClient = null; + var reality_zone_gifurl_list = ['bogus_url1', 'bogus_url2']; + //var system = null; + + var tic = 0; + var toc = 0; + + var desktop_connected_hack = 0; + + var testImageData = 'fake image'; + + + server.subscribeToUDPMessages(function(msgContent) { + //console.log('received udp message with content: ' + msgContent); + //console.dir(msgContent); + + try{ + obj = msgContent; + //var obj = JSON.parse(msgContent); + /* + id: thisId, + ip: thisIp, + vn: thisVersionNumber, + pr: protocol, + tcs: objects[thisId].tcs, + zone: zone + */ + + //check if its an object on the network: + if(obj.id && obj.ip && obj.vn ){ + + + var objectName = obj.id.slice(0,-12); + var httpPort = 8080; + var xmlAddress = 'http://' + obj.ip + ':' + httpPort + '/obj/' + objectName + '/target/target.xml'; + var datAddress = 'http://' + obj.ip + ':' + httpPort + '/obj/' + objectName + '/target/target.dat'; + + var messageObject = { + id: obj.id, + ip: obj.ip, + versionNumber: obj.vn, + protocol: obj.pr, + temporaryChecksum: obj.tcs, + zone: obj.zone, + xmlAddress: xmlAddress, + datAddress: datAddress + } + + for (let station of stations) { + station.emit('realityEditorObject_server_system', JSON.stringify(messageObject)); + } + + //console.log('received udp message: ' + obj.id + " " + obj.ip + " " + obj.vn + " " + obj.pr + " " + obj.tcs + " " + obj.zone); + //console.log('xml address: ' + xmlAddress); + //console.log('data address: ' + datAddress); + } + + + + if(obj.action != null){ + //console.dir(obj); + //console.log('received action: ' + obj.action); + + var action = JSON.parse(obj.action); + if(action.action == 'advertiseEditor'){ + console.log('action advertise editor received'); + console.log('width: ' + action.resolution.width + ' height: ' + action.resolution.height); + console.log('editor id: ' + action.editorId); + console.log('completed reading json'); + + viewer_width = action.resolution.width; + viewer_height = action.resolution.height; + + //send a message: + var zoneResponseMessage = { + action: 'zoneDiscovered', + ip: ip.address(), + port: 3020 + }; + utilities.actionSender(JSON.stringify(zoneResponseMessage)); + } + } + + + }catch(e){ + //console.log('could not parse json message: ' + e); + } + + + + + }); + + io.on('connection', function(socket){ + console.log('my ip address: ' + ip.address()); + console.log('Client has connected'); + socket.emit('message','hello client'); + socket.id = Math.floor(Math.random()*1000000); + var temp_ip = socket.request.connection.remoteAddress; + var parts = temp_ip.split(':'); + temp_ip = parts[parts.length-1]; + socket_list.push(socket); + + socket.viewer_width = viewer_width; + socket.viewer_height = viewer_height; + + socket.resolution_set = 0; + + socket.on('disconnect', function () { + //if it's in the viewer list, remove it: + var index = viewers.indexOf(socket); + if (index > -1) { + viewers.splice(index, 1); + } + + index = socket_list.indexOf(socket); + if(index > -1){ + socket_list.splice(index,1); + } + + if(vuforiaCamClient == socket){ + console.log('vuforiaCamClient disconnecting'); + vuforiaCamClient = null; + } + + if(vuforiaResultClient == socket){ + console.log('vuforiaResultClient disconnecting'); + vuforiaCamClient = null; + } + + }); + + + socket.on('image', function (data) { + //console.log('station sent image. viewer list length: ' + viewers.length + ' datalength: ' + data.length); + + // extract editorId if included + let editorId = null; + try { + let parts = data.split(';_;'); + editorId = parts[2]; + // console.log('received image for ' + editorId); + } catch (e) { + // console.log('error extracting editorId from image data', e); + } + + if (stations.length > 0) { + for (let viewer of viewers) { + let viewerEditorId = socketToEditorId[viewer.id]; + // console.log('send to viewer ' + viewerEditorId + '?'); + if (editorId && viewerEditorId) { + if (viewerEditorId !== editorId) { + // console.log('skipping mismatching editorIds'); + continue; + } + } + + viewer.emit('image',data); + /* + //write it to file for debug purposes + fs.writeFile('debug.jpg', data,'base64', function(err, data){ + if (err) console.log(err); + console.log("Successfully Written to File."); + }); + */ + } + } + }); + + socket.on('name', function(data){ + console.log('name function called with: ' + data); + //console.log('data length: ' + data.length); + //console.log('data = viewer? ' + (data === '"viewer"')); + let parsedData = {}; + try { + parsedData = JSON.parse(data); + console.log(parsedData.type); + } catch (e) { + console.log('cant parse ' + data); + } + if(parsedData.type === 'viewer'){ + console.log('client identified as viewer. assigning it latest viewer and id: ' + socket.id + ' and editorId: ' + parsedData.editorId); + + socket.type = 'viewer'; + viewers.push(socket); + + editorToSocketId[parsedData.editorId] = socket.id; + socketToEditorId[socket.id] = parsedData.editorId; + + for (let station of stations) { + console.log('setting resolution for the first time: ' + viewer_width+','+viewer_height ); + station.emit('resolution',""+viewer_width+','+viewer_height); + } + + //send some matrices to the RDV: + console.log('now that a desktop has connected'); + + var visibleObjectsString = "{\"amazonBox0zbc6yetuoyj\":[-0.9964230765028328,0.009800613580158324,-0.08393736781840644,0,0.022929297584320937,0.987345281122669,-0.15691860457929643,0,-0.08133670600902992,0.15828222984430484,0.9840378965122027,0,329.2388939106902,77.77425852082308,-1489.0291313193022,1],\"kepwareBox4Qimhnuea3n6\":[-0.9913746548884379,0.034050970326370084,-0.12655601222953172,0,0.051082427979348755,0.9896809533465255,-0.13387410555924745,0,-0.12069159903807483,0.1391844414830788,0.9828837698713899,0,212.63741144996703,206.15960431449824,-1826.5693898311488,1],\"_WORLD_OBJECT_local\":[-0.9742120258704184,-0.00437900631612313,-0.22559212287700664,0,-0.035464931453757634,-0.9844127588621392,0.17226278091636868,0,-0.22282991544549868,0.17582138398908906,0.9588711290537871,0,161.99950094104497,-166.5114134489016,-519.0149842693205,1],\"_WORLD_OBJECT_PF5x8fv3zcgm\":[-0.9742120258704184,-0.00437900631612313,-0.22559212287700664,0,-0.035464931453757634,-0.9844127588621392,0.17226278091636868,0,-0.22282991544549868,0.17582138398908906,0.9588711290537871,0,161.99950094104497,-166.5114134489016,-519.0149842693205,1]}"; + var visibleObjects = JSON.parse(visibleObjectsString); + + var zoneMatrixMessage = { + action: 'zoneMatrixMessage', + matrices: visibleObjectsString, + ip: ip.address(), + port: 3020 + }; + + utilities.actionSender(JSON.stringify(zoneMatrixMessage)); + + console.log('looking for objects: '); + var pingMessage = { + action: 'ping' + } + for (var i = 0; i < 3; i++) { + setTimeout(function() { + utilities.actionSender(JSON.stringify(pingMessage)); + //realityEditor.app.sendUDPMessage({action: 'ping'}); + }, 500 * i); // space out each message by 500ms + } + } + + if(data === 'station'){ + console.log('client identified as station'); + stations.push(socket); + socket.type = 'station'; + if (viewer_width != null) { + console.log('sending resolution to station: ' + viewer_width + ',' + viewer_height ); + socket.emit('resolution',""+viewer_width+','+viewer_height); + } + } + + if(data === 'vuforiaCamClient'){ + console.log('client identified as vuforiaCamClient'); + vuforiaCamClient = socket; + socket.type = 'vuforiaCamClient'; + } + + if(data === 'vuforiaResultClient'){ + console.log('client identified as vuforiaResultClient'); + vuforiaResultClient = socket; + socket.type = 'vuforiaResultClient'; + } + + if(data === 'realityZoneControlInterface'){ + console.log('client identified as realityZoneControlInterface'); + realityZoneControlInterface = socket; + socket.type = 'realityZoneControlInterface'; + + + //find all the gifs: + var gif_url_token = __dirname + '/public/gifs/*/*.gif'; + reality_zone_gifurl_list = []; + glob(gif_url_token, function (er, files) { + //console.log('dir name: ' + __dirname); + //console.log('gif urls: ' + files); + for(var i = 0; i 0) { + try { + var poseInfo = JSON.parse(data); + + var cameraPoseMatrix = poseInfo.cameraPoseMatrix; + validMatrix = cameraPoseMatrix; + + //with RDV origin mode: overwrite with RDV camera to RDV origin matrix + if (poseInfo.cameraMode === "REALITY_ZONE_ORIGIN") { + validMatrix = poseInfo.RDVCameraPoseMatrix; + } + + if (validMatrix != -1) { + var data_for_old_unity_version = poseInfo; + data_for_old_unity_version.modelViewMatrix = validMatrix; + data_for_old_unity_version.projectionMatrix = poseInfo.projectionMatrix; + data_for_old_unity_version.realProjectionMatrix = poseInfo.realProjectionMatrix; + + if ((socket.viewer_width != poseInfo.resolution.width) || (socket.viewer_height != poseInfo.resolution.height)) { + socket.viewer_width = poseInfo.resolution.width; + socket.viewer_height = poseInfo.resolution.height; + console.log('setting unity resolution from valid matrix to: ' +socket.viewer_width+','+socket.viewer_height); + for (let station of stations) { + station.emit('resolution',""+socket.viewer_width+','+socket.viewer_height); + } + } + + data_for_old_unity_version = JSON.stringify(data_for_old_unity_version); + for (let station of stations) { + station.emit('poseVuforiaDesktop',data_for_old_unity_version); + } + } + + } catch(e) { + console.log('error! could not parse camera pose info: ' + e); + } + } + }); + + //use this for now on, hisham - 5/22/2019 + socket.on('poseVuforia', function(data){ + //console.log('pose vuforia called: ' + data); + if (stations.length > 0) { + try{ + var poseInfo = JSON.parse(data); + + + + //socket.viewer_width = 640; + //socket.viewer_height = 480; + + /* + if(socket.resolution_set == 0){ + console.log('setting unity resolution to: ' +socket.viewer_width+','+socket.viewer_height); + station.emit('resolution',""+socket.viewer_width+','+socket.viewer_height); + socket.resolution_set = 1; + } + */ + + + var vuforiaObjectList = poseInfo.visibleObjectMatrices; + var objectNameList = Object.keys(poseInfo.visibleObjectMatrices); + var validMatrix = -1; + for(var i = 0; i 0) { + //console.log('data after parsing json: '); + //console.log(JSON.parse(data)); + //data = JSON.stringify(JSON.parse(data)); + data = JSON.parse(data); + + var zoneMatrixMessage = { + action: 'zoneMatrixMessage', + matrices: data, + ip: ip.address(), + port: 3020 + }; + + utilities.actionSender(JSON.stringify(zoneMatrixMessage)); + } + }); + + + + //custom vuforia server: + socket.on('vuforiaImage_system_server', function(data){ + console.log('image received from system'); + console.log('data length: ' + data.length); + console.log('first 100: ' + data.substring(0,99)); + if((vuforiaCamClient != null)){ + //if((vuforiaCamClient != null) && (vuforiaResultClient!=null)){ + console.log('passing image to vuforia camera'); + vuforiaCamClient.emit('cameraData_server_vuforiaCameraClient',data); + }else{ + console.log('could not find vuforia camera'); + var response= new Object(); + response.message = 'there was no vuforia cam processor'; + socket.emit('vuforiaResult_server_system',JSON.stringify(response)); + } + }); + + socket.on('vuforiaResult_vuforiaResultClient_server', function(data){ + if (stations.length === 0) { + console.log('ERROR: received vuforia result but there is not reality-zone unity system connected'); + return; + } + + for (let station of stations) { + station.emit('vuforiaResult_server_system',data); + } + }); + }); + + var last_n = 0; + + http.listen(3020, function(){ + console.log('listening on *:3020'); + }); +} \ No newline at end of file diff --git a/interfaces/pass/public/ball.png b/interfaces/pass/public/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa9bd4a532149e0e95843270861ac074beb0054 GIT binary patch literal 14499 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 literal 0 HcmV?d00001 diff --git a/interfaces/pass/public/clear.png b/interfaces/pass/public/clear.png new file mode 100644 index 0000000000000000000000000000000000000000..41d110135d588c12d5280ae7a6afacbd6e019def GIT binary patch literal 13857 zcmeIZWl&sA@HcvP(clDk4-jmD;10nFp5O#`cMB{D8l2!PZb5>(yITSTcXxLJ?>_%~ zzuc;OtM2>#-Kwp#bGoOeXJ%)5y8Acbs>-sMXryQW0AR|?y;lbSF#IPNKt+T<44uiH z;SWShi4PJ0P!)szWQ+taQ=7`Ee*gfVHvj+)0)PkjE9f2oxI+Nozz6^YQviU-3D&G8 z3@3n16lLE7KuHuG7rgY!NlwQV-qP%UUyvkIx)=N{UA2Y<+NEM>LnnMP!mu1fE4%TbIX3)79dHhX8BkI6OT!c zTHqWHG2#y1K5D*G{lH*uw}C`>K`Fn8v@MDRUCh<*1dr#UNYh_=6;aFc$j*(SQJUkr z*N+U@`$#r3kGnaTV0_uj_Wbvb$0@kOX^q!c!9!RXKc%nohZrDf~#S;MB1qwFdT zLOS-t)1z>Xy`#AC^OB}(LiWjLx@l6UH2BE8{i1B&olK-bG6Xu`e!v}uB^3|qCp<5e zkBgO>_@(;$kd45=)UL;kX@T_-sI4^mImKG3=r)%;l0pj_av&u7Fg#j?cVm=sCgoaG z-`#0M2MnZ}=yh-qsnh1ef zQmFaQ5A|^q^1Sdxb~(S&w3#v!B&w?PHuABy8*v9D@i4I9?}#`h)9*Vw5vCqzZ#b&r zDadnalOIrA7y(-RKjBrQyM7Pz>@qH@@ocSwQ>`O8O9Ga$WFQJhA9G^ZqK`rl9 zM6WDS?>GU;t8LP96QHlX?6b?5vFL^brA7LSGR}jeoj$JX9TqyVLXP3{#w%|(OGLV# zCJn3XrY98uDtFpqW0btx%zKOTScvqMIlTdYr@vSo!V^Al+!VXm6lp9t%R!lt&G>C! zF`nYt8s&}=2ytR-;L0esxr>kuW-Ygb6esN#VN!qaNA`PY8CUHd<3cX3O_S)TRFDi#3gKV#;%iF z_@>zsknA1S{Of%lC3g(5pr|5^@hy9~g&lXQDL11g9D2o9 z4vtVy_|&R6>{`-KA99Ei(DvI!SBIM#MgbMb;wdYB!Kc3UqH)tfsq!LZOqUjjUe`!} zV@}WP+in%HP&rc}hx{fju{&PeK**V=-;4(?E!Hc}a1Xyh&|)xbJOTuPIXdYXRp`(+ zS<~Z+-WVc!aR4C~<_?9E9wfBU-??BoR7Kvn+_TN4bza6bgKYhTJve|#KC-wBPYm35Mi?X<++3E#Ry31j~++< z&i*`PY%HiY>MvSpFj&-z2mH8u`=hS6XsZ#muhtY#$e!rJT3}@|xxy8cVV6v^(Ttc8 zeokp#j}EFp6c=qc6Y!a5m+B3ZY*y=UG2DA~CIgJZHyF*`EJPV01?>roMRPAYz~|Dm z-@1z9RV?IqzpL~H8{r5=9NtwP3b;fQO<1|+S<*2J#v+vqceLeR;Z6slGJ z{^|CRZneeOv?~j-rvpMD--3dVzk6wYCBfkB(76P~W+%Mb@zm_y950-H8WO(*O(xg8 z1LU^eE%X0aDde*e1SBKPa`!Xv&x32mZlhAMp!_{EAf`WMY8ZT9aEt`u64gsz!rzrb z)sa({^Y;PbF{>Uu{PP&jNX{0C8W4^-ztreSc32EX-t-YEg#|*k7=jx>{U;ZUoMJ#X zVh3Opb$kL|TdRdVV{kXYE2t_2x+6>=>!&b>ShVK-{H070v;wu z{x&nxipI&RD2&>Vn*kWVA3NmIn^R%b4C4G=Z+Ki~@f?wRf%MG_YiBwkDk%W{V_n3XpHdmnsF z7hj*(c59`bhsQb|9RHb)s2&_Iz?Sx7wm8UdDhBql0UUXqaU^jX zH7UK<=N&(bT`da1;vxWTF?xElPhoMgxe|yvzPhql%0dJ462hZNvXB(EI+K>hZ}}GR zID;r+*emv)dQ>lZyrTWz`E10-9)oppjB!3!83ct%`A>vSFRX7utDvP^lg!V7rS2GMtk;7*l;`ylhyF=XDFEP8oKag+zO;rDO2p~8y{ z-#eIqCljFvudj6pYSL_V(|licnOb!E^6pH06aGyzUii2Lb6QnO0A+5Vo=pCq^Z3CUiq(pWbAAkJ2oG7%an(=91iAu_>(AQ z$43q__j}KWtZw91x{?dXwUlS0h$AW9amrS=ChM-4Q}tcZgWm5e5_#0{Y*QjxCR9bJ zD}<3Omn3`f0&awz=?C-O%SUc&nWSE6J=0E<(m`Ra>@x{fqi0jj1Oa&*1%aX%XY@!= zK8hWNO!uReBvon58s<0eMY)T2m`*L7o#Mg=QpGt0NtX+2LXGW?& zQKDP8v=SVGHC%^15m%b(V@40iZV#!J*r2cXyJz+E>D~!ESgVJRPp~3pgls$_7nEa+ z(^(Tkvxaqh1v2<7YIJd(c&_`;o-1bNqd^_?``kmY0bB1NEeubw|6#bq3UDd*eirpS z_u7O(U;7Ut_W0g_&~;^7J4&T#rTS^q_Ve#k1Gw$q2BJc4%!h(-zfTK>o#*vdd`gPfwm^Pv~Y<@|y zd+nJ)r%xj~b2YY%325B{(7ng59ja}TE>Br^?=C1-E%Sfl`Xsh?nIbnZ{!$0LhG+>% z1cW|!sr#>Z#j37sfqq$lr3g(%E-MNZ{x94 zYj*_m%av%R87E4Vs30f@IQuF{Y56J?(~+kOvE25l6@AwEjkD>Za&?4QC0}q>nHh7( z<|=cZ_>b_6A~Y|>F~Fg_(PX+*{7 zVj%uPG5Rd{4e+Qo8Qxb6ybwP0Y+RZ`)(VTD`_!%%dJ<(z2I=X)q3v=9&92M5MJK z!IpTw`!*LgQ6N)Vydg^t7;&#|s-K7!h;1dieY(x^g)hoXv`iPL?Nvc22}|W%=x>G#ZY|Shljc|eA2VQAY)K}@=~d9K@?qQNyY9Vz&@0w8_HP6; z7pCz?>uc6AGD6RNdp1a*d?`Th=G<45t9|BKzqgn7`QQtGN1qg;XPG61Z@TyW=Pvr5 z7?m|9;E2E9<=N!vpV+Y7PQ7<<59G>K?L46POQF8uLw5&v~5&;1aniD;U@~hmbfRz~08F=~Jym zgp=k0kczaA!d7?P`oI*w*!C+PgVY|QO#=VIAX{QH=f}Vt0OE+Ja*=ud>t@|WCjaR+ znmeJitPb=&Wz;X@AD;xsXo1FXhh&pwLpo((y0&~wZ*=>LTYuZxIK+iOq|8pp2_LYS zxx`YbUcU1PE%@gW%{iz}mW|GxVr|?4*AHTgf}UR3l%mnOOOkODn|8BgWsDn5jMuny zml%Pd2r^P9i0~R;izmxU>tt|Kd~C?Y*s$5GOL7v$i*n?t#0Pfhbp|B=lC3(>cuq3r z_4|G0ATtc-7{}Pb~?k zOAR27-1;CWw~js)Gbd~_%C+4q&DW!zXct3szBrE zD_+vi_hK=eOKWpNHjxqg`zCeAjdAK@&kYiwwRTSZ_e4E0ZjR-yZlW({A8?E@(@m*ua0{nDvjWLUpwY&&m}ef<1Q$6|O#+!2>;CX!^Nf&cZ#V zcB1=|+9#;o2`m(1xtV>gb_HF*vb!#C%=tBSstW-~9t9D1NR{w_LVZgsxtx{-q-XuH zj6vpW7&-_734eMp7s{+`*xZ5yw~!2r{l9 z1d662pJAOb74MZgS(wDZ3r6WM!RIMTEnqA45x<2lL;R_2d37bd22N9qO0K3Nwt` z#RuN~%!>?7-4oZ+<~u2Td*~y&!-zq*e$mSWVkSU;g;Y! z)lfCSO9J$-F_>9+oX&3eOjMyM7Nj%n6e%s->2jmK{qVfQon))QucbCY>fep#@lLr= zx9c)8yW;Q~DGc06Ch2A&+$NCTaW0JV=lLJgEI0cmR-aGO1>ktbxh-?!fjV0!pxG4I z_LPXCt%#I`l~%Im-S3p=Qx3}m&Z3FG?DbN=IVV<}f{FXF*Kjpn4LD=6?tHArDG0JD zDb9^2B|`q^U&6iX;!no0(Zjk^(b_Wr6E<<7`2nC0l`iOqB2-y_Oa; z1_qM2Vu|($Y5i+PWtgAIRvfi5rSrxX2)8>Z{I%$MjfxmH9*s-4C+&w-MJ<|H?$k%` zz&dh4d#G|G2lw1=K;K*53-W-CZwV8A301IxCIc-n87ipp~KV)f~U(<(8Wg%Zg}BCYpEY z)^QGl!1TU|WQh(@emZ9{RCGn`_Ba`FS^EK?L6d@+He@U=Cxk}~ZIsai=|5^pq!B$S z#L9jJZ)~ZF!#sM2=(3298-rf4Wv-xcM&q`2inMmE_j?ZGP~5f0Bu^=*_fh)y1h~H2 z#>>nq*`_<$q2fu0Xn9+6O)8G&=LPQU)RkG)Bq2Nn%5UKzUo^%F9>ypaQn{0_sVQ*# z*GgXPiX8gJ9fVpkB6uezgQ?{iI?PiJ5mbd-`0AK7Yk~s`QB=Ft4`Y-Hsp`tMcUY-@ z`w?od#%PKOlL3auGU5N@0R5&P_F|{$B^=gYQXn`cNswIiz|}}qk?nW+ag6dtYVl5` zeUz(OkGL+{9yWxtDD)#9zqQ$C`|2Jnk=l{0FP^n*Os#&cH^-At?$e)x1-M^0Tv zBY!$W_wNh+g<+>48~^RfBmuj*T4WKd3gpu%_#$%#I@oPjqTgoa!`dTVy?H40JkETd zqxz0qotom1MYmoHdij5?{Lg)AB?fS4V;mB=LYT-5v{@gC`(P4P$IEW|3=?OL!rh_d zu6LQ(8l5^xp^^<_HK4VB{XcGdnR4*{r4D{$b-y#p4r%aS{(bo1(?xo+%fQ?CCs=pl z%jgDq1*NHP2AG-~2_}I}s(o-L5->_On$vrOC1@cQQyErp}jm=TV z!e(o8^AyL#TayIer_?6$T8-Co-w4QnV%xcr&MHS1`8!$sjC^Ie@lEg>boin3y{>jn z*0CPr>Og*JV8eaHA!A1T(auB&+3)fF!FM})#FI}L9L1Qilt|*u9v8wDBy-~ul%_Gq z8Dn!m`@%t=*phRYG&{!Fur(U=>Rn}62eGxqoB07pyHkgLlgKp`!upmJMy|%{|9buB zl2C>0(3X8ulu9gBs0y0e-=I5Ul3Cw&=nwakWl1K^ORDHDk#C$Ujt$1O!%m*Q1>4@) z8_`hBuXw+}|6-d0gwDNdHzjw=@*~hkMOf96go(1*M;2)jAh$dq+(VkD^2>+! zJSYModI=sw_JmB{x!Un@ToxW@+@d3immGM1Wr*gm{GFux9Kp3(59-F)Qr?VkpT3|B z;CW8IV1~1-nB-IyvoZRFGKh*cP;4lS^R#OZNEmK1EVi6rqd}+1w`o4m@fLmr@A!*AxsmbREH{no#$Y;gkPec7i2hf8l8=&5`eC;z?&Vx76zRBQhbNT3t zlw(@u%Cv?rB9y>IQ;&OVmbIV?<5*zZJBVi+c@$aaKa&4!;4kVf)iYEw~P!dIUEY zk4AWwx@VqXj(@IePm^kTckO|_A-h77l^x`aS61P0ZLa&Zwhjlu<%NtHsiQAz?Ce6v zwet!|cTdZ#!`g(z?N`_UF z7l8>iIXaUyIE3B@-_(}yXpqsf2P)ILKtC@WDuJ; zK~j%y82>q(3KC^wcy$lwH94`fDEme>Zg(INrvaCYw|iO6N4a>3&|lDMVrc2GrnMxA z$?@oKr3mA0n(37I6v^DLMR$Su-;lt1?#pkeWEYkiqNpZ(dfpltSd!^K{4xl%M*yd{Oiwfb=dle z_Kwf!2#j%7J!wAKHHO|YO_=CQ!Z_3phCA1U+fB27{GPp9%Qqn=ni~7kOOT?2s z*SkYNDIT^lQvd8=<4UdhaFN=x>zZ9U{SghS+1r%Vg~dA?ArMpQl5hQBE1DZ!Ql_jR zlQN+h%0ZoGgt5vam@$%!4-}J(llus|edZkBvhbN-VL+L_WXD#;)Tsxug=V>zx-3m$ zxK9*#Y+|CK*?`;!n4-Y96gs%~$l|h5oVrEaVIhPmZvM9?e%v_PG$#cfzf#!ekv$PKxk$ot>2&( z^noj;xZblDcbnD1cuH%&0iYfxUorP3Bsym@gAh3rn7VE}eaD6G#=)t5LUv;&((Qii zYu&Jvbi)A||A20~_4hz0HMP4|p}RUh-LvzVNO#k2e=7s&te-!9Wye@kCxnQf{@Ygr@^|%DEKEa2f*VXh8g{2p zIvbsyWyUrYp(!wQd~}U1Q;|;jf;l&#G)0Bu+NwYW6q^}1y7uv}#aTLy1?N-&=V_t+ z`YYZ{w+IaofY+43mo7-8M@TCP@PU1@5rQ(gOA1GvOM{dXlO7tw?Y=1ocP^kia>M8Ox^@wr zWT2!kR#gZJ$v&V*XG7X?PXEd0zcu4I8$QFJZT!1+#gr=YECQ!~S()HM_UzNCz7 z+Sxq4?KbLn<;7#piS^qka=$rVdqtVcy*jzq;Rd(%L>7ozHLOvkmyx^Rp4lGv5ysZe z1B>w~p?NTrspp>jMe>y;XI#y`g73oANv-EU`bV!+E$>hR@*Nb6S@}-52(I68@=Ch} z_yK~WM8qk0F>WFpm%@o-egpFWw0vn;$1Ml151)_wRcZWK>qvT;1@tjBk;v%kZUS%NXb$SO^jrR&<8gsvS?0TZBy14ja2Muid(fyGN~Sdv#?JH z1d)DzmXEH?T0SyE5t<(ny{U{qn1)?V?LwrbpximYpi;`pQg1x|n{wEY9U7EpX@GP= zO-=}nnO=g@dn-{tHt!k2&tov-}fnf&zz<0D5_>%P8TL3L5Sf1@SQNNrW} zKYFu$)O3n8P0&yl2<7jUh;~Z(c4SKRSZr5c5I4Ysaix~v69%T%I8Nzz%e3DtlJpCt z3<{jp8K}4Lr;Sqwdc*MOlylE(h&gHL!*CdsYtIk))pC2onjuU#)G;;9eYf{tp2Bl2 z5U5wQ@6W$?7ft^a$5d6steaL{RszTsa%$XZcXtNCrTy@EA;?AlyyiZvd!GvVrY(-a zKkRbD`^Y_`Sp>OfP;M^8jjqWRI1^NS$Q zrod;Sm(c_`REGxb9=SqorTgg`V$f86ewnQ6_2Q{$fbOi8^xK90TDa??7CZrl`cgEnq1wl4CCqKm6JgEavYn&^UMo5p zw@MVf-9DGPHZg}U(Y7Idx!=|e9y{^<6NcuNq{q0b&tf&+SXG*v$TW>;(UQgPmk9URRC`6}QhvY&J^YU5{j=(W-S zQXR|kn&tkL{DZ)WcXyt>`vop0%oz_s#fuMZ>IX?wR>yG;0`v-;9VqhjC$H^g^4ntF zlk5#XFizZ6hF4Ch-y7DC3q8~SL}Z*K7U0!NPkh z1A&_TOCpi0=PYGFvnJYC#yt9w%;WC@Pl0&K~%n6sj$5sat; zcJLOr>$6i_Q|wy8ma+jS1y~bIK5|p5m<&yvf`$K}*mbWMS)9c2BB!asB8#qbu$7NG>T&+KdOpQRg1VZ(Cg?ZZOr18_ z+*^%Xe*OpwE5Zj*ynm(Ey|mH1XLh~*AGRL~smcL@zsaEVi6jQ^M@8Zi{-TwbvSBwg zPlkW!Q`#YicN8ls-$Omfx^=9ppA&k0bTLYwy{Yc|vf9JKXz;M8*d1xE%#BdNAcY24 zVr~la@O zIJ^xn!D3 z*Lr0+&clhkz`kDlUH$NKf5bdWG0m%|AoA}+zzIAM&Nwc&$N>AKW&?^f!r=R};?Wf4 z=%Cp?>x#tkQhbA^HGBlzV@*Z@>`c-m*9 zv4xS(_Fm;bBqknh*8P7p!HBO!PFS-(*+{MobUe=!B4_|#U*lIl?2I<$HY7l`cb1fh zua4n8=jWL-Dn4dl3E%wnW?DIp(dUCN0D$XrDK=C?Y~);S(ilqFK@P`hYNb^8T7e?2 zbOD@QSEV`+Vw_yM)%x1wPIym_-@frG#7#e`3#1t}aI2g~wi@`Dge?IU2d&jZII{iSH|m=bS-4Q_ zl8UUMzC(fvsL4#l(X@8Hu2*&RdrYMS$Y+yT?!yY`4N5D}^HREy(jFb-|4R=1+Z!i- zQ{<$wi2i8VTnAt;uf0vPfS3Rt7||FCiww8Cd0ZJ(v;>g{K{o2AE_jV{YgB z9kA3S2xg{LKe$i5*eW;nHT4ABVS#|}DHA@Fd2jo&uyJV=V8onov&r{QOyaB8X25-I z5z7~nap2d3B|KXbf-0iSKlE{|sz1i%EtBKJ!5v%TwCdt$0ME2~`rnIXZyl9VioU)w zQ}>$H!w=XO;jd{V6PS!mdc!ruDevhA>9YJT2J@iZc%tuSX5s1bFRxzXH{2}?4l_AE z-5u3Th&pCR>mK~HeF$KK=QRCioVrB_EJPS-y1mOR(L>YkSCrU``0lrG87s~|p9{m& z?b7@2i$1k~mLf4v(D-}x(nG$6vz_3aU~}-ZDB9ik= zo>r$8fJ8!NJB7dRUaS7$2^%4HUHD}Kx{C*k|KW>v%=nw5T@0MjAwlyiVr!b#6BhA- z_Ix1rDtK>#4MT9Bmje7BD;I8Ff@9`8D=jwQvpOJW@x2i`&*=9HIAcJ9n!S;vYWMEO zVfepU6OFj|c)k=6LOGZLdJUR|a|Q)6EfQ7qPPq3I--KG=7wi!PFTwZh_>2k8V8j7E z;N%w}J$(O^^Z0&G`XyXdc1-?9Odk=P;TL*iSoGT7fwV~&SiG8G-8Pb5nV_y-G0x3e zu+q#Cb+#tL1_}D|6Ny^6F|H>3E$}4E1~^NCJ{RhL0iN>!980AfeU_((7_c}@{imYO zUCaZ}%j|Ri82kSUcN#4P#ZHfM4bfzN;q~PM-jyi1$o|rz>nsILW;<`kOW_073N~Lz z;B*7lz7~+GV%;(AdvZ0!D6Q4;R#HH9z<7}%jDLfv4(hV8{5E~aSI}@Ec`5$4aDkKX}}NnHuZDt61})tUbhML zo(S3BKNyO*)5eF;r~iZ-8pT#J!2QTF2HteF2efU4V(w%akW(KDt&hMNT9A3$x|nqB z0EqLPF0f0`+n)L~pmZLBD!buL>iEKsC8;)_d~dJ9A~u6cYSQJXIsPOHB_5oGy3C1{`&-&&qWYHodI z2HJVZ21WKb=e@2U!5NaHV42_9PVSgR0MnEDi^?e@#j;Y`e|1GjX&`23}+kO3wb>OY~RN_swW)aj9jQqI6*R>19<0@83S*0yDzhpEzQE5(-Gf+Y4{RxN1G40$xA1n^nW^dX1B6raf?r&lQ1$b40Qk zctyGDaK?Ww#zGy4K z%@f^-VTDhs;1dB*8Kta53lBHNSxh_e-Krb6M7rIv0VVvBj52U|Y(7bh6JI*qDpGbJ z0Y^n4>OOPqQd{`8cPfULJ$Em7_Z$G}oFbdXV2wf8;H%*~G2uSWMf3cT01&`)R21my z=Z>b~q8Gn9SIo#?I5s;wu;(Z*!LS}37xd{I zedT<2qwX04_-RgsLM&BvEJ=S>f5B~-4+r$8p4$TAn2SHnXp!pCMtLv_fTAVgm;w~|pp6or zdRGpy9q6$nERF>}FsucUU2gCJ+p5KUVtF}<;d#-B69NI4aF~4tamgoWV{TyZ6I8ai z!63}lg&6>DD&rMo;5Qd=h6Fr(NC){$Rc z#;zu9Spec9q4T}EH=J^&hzwHlH|Ki(V2SE`gW_gzsPp`nfvf&xeXO(S#0NCP8+E+4W6>(`C zqf`EAiCyX)!Kb~`!Q($g6bZngxvW#EopKLW^3K+u2!ympTntUkaSWeu@&O=>Gcr}l z1fDI=>*lE(>PwBk^QVPh!*HVgKjoM;x73kDBbcjsHs5rO7T)hrhF{ksSR^k zEz4iV&=01>B>4)tzJo}c>jc`Tnh}pc!)(TzAxV5rXy8>IhseGHX9hF1l5d9SRjSW$ zaZx-1xsh1Q+%Tc21$XGTC;x!v7wkJJmM@Zo$C&WjPz-L;+HPjXZsvlfF6QtDzy;wH zV1w|maYHn@xCA+Q1UdOxAY6hFh{7Th&Htl + + + + + + + + + + +
+
+ + + + + + +
+ + + + + +
+ + +
+
Icons made by Pixel perfect from www.flaticon.com is licensed by CC 3.0 BY
+ +
Icons made by Pixel perfect from www.flaticon.com is licensed by CC 3.0 BY
+
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
+
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
+
Icons made by mavadee from www.flaticon.com is licensed by CC 3.0 BY
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/interfaces/pass/public/jquery-1.11.0.min.js b/interfaces/pass/public/jquery-1.11.0.min.js new file mode 100644 index 00000000..73f33fb3 --- /dev/null +++ b/interfaces/pass/public/jquery-1.11.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n(" + + + + + + diff --git a/interfaces/scene-graph-visualizer/public/visualization.js b/interfaces/scene-graph-visualizer/public/visualization.js new file mode 100644 index 00000000..b1adf28d --- /dev/null +++ b/interfaces/scene-graph-visualizer/public/visualization.js @@ -0,0 +1,383 @@ +var clickedItem = null; + +(function(exports) { + + function getData(sceneGraph) { + console.log(sceneGraph); + console.log('... processing ...'); + removeRotateXNodes(sceneGraph); + let data = convertSceneGraphFormat(sceneGraph); + console.log(data); + return data; + } + + exports.visualize = function(sceneGraph) { + // reset SVG if it already existed + document.getElementById("my_dataviz").innerHTML = ''; + + let data = getData(sceneGraph); + + // set the dimensions and margins of the graph + var width = window.innerWidth - 40; + var height = window.innerHeight - 140; + + // append the svg object to the body of the page + var svg = d3.select("#my_dataviz") + .append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(40,0)"); // bit of margin on the left = 40 + + // create a tooltip + var Tooltip = d3.select("#my_dataviz") + .append("div") + .style("opacity", 0) + .attr("class", "tooltip") + .style("background-color", "white") + .style("border", "solid") + .style("border-width", "2px") + .style("border-radius", "5px") + .style("padding", "5px") + .style("pointer-events", "none"); + + // Three function that change the tooltip when user hover / move / leave a cell + var mouseover = function(event, d) { + Tooltip + .style("opacity", 1) + .style("position", "absolute"); + d3.select(this) + .style("stroke", "black") + .style("opacity", 1) + + console.log('mouseover'); + + showDistancesFrom(d.data.name, sceneGraph); + }; + var mousemove = function(event, d) { + let sceneNode = sceneGraph[d.data.name]; + let pos = getWorldPosition(sceneNode, 0); + Tooltip + .html("Position:
(" + pos.x/1000 + "m, " + pos.y/1000 + "m, " + pos.z/1000 + "m)") + .style("left", event.clientX + 'px') //(d3.pointer(event)[0]+70) + "px") + .style("top", event.clientY + 'px'); //(d3.pointer(event)[1]) + "px") + + d3.select(this) + .attr("r", 20); + + console.log('mousemove'); + }; + var mouseleave = function(event, d) { + + Tooltip + .style("opacity", 0); + + if (clickedItem) { + if (clickedItem !== d.data.name) { + d3.select(this) + .style("stroke", "none") + .style("opacity", 0.8); + } //else { + // d3.select(this) + // .style('r', 20); + // } + showDistancesFrom(clickedItem, sceneGraph); + return; + } + + console.log('mouseleave'); + + svg.selectAll(".distanceText") + .attr("visibility", "hidden"); + + svg.selectAll('.node') + .attr('opacity', ''); + + svg.selectAll('.nodeCircle') + .style("stroke", function(d) { + if (d.data.name === clickedItem) { + return "black"; + } else { + return ""; + } + }) + .attr("r", 10) + }; + var onClick = function(event, d) { + console.log('clicked on ' + d.data.name); + if (clickedItem && clickedItem === d.data.name) { + clickedItem = null; + } else { + clickedItem = d.data.name; + } + }; + + let rightOffset = 300; + + // read json data + // d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_dendrogram.json", function(data) { + + var tree = d3.tree() + .size([height, width - rightOffset]); + + // Create the cluster layout: + var cluster = d3.cluster() + .size([height, width - rightOffset]); // 100 is the margin I will have on the right side + + // Give the data to this cluster layout: + var root = d3.hierarchy(data, function(d) { + return d.children; + }); + + if (useTree) { + tree(root); + } else { + cluster(root); + } + + // var scale = d3.scale.linear().domain([0, width - 300]).range([offset, width - 300]); + + // Add the links between nodes: + svg.selectAll('path') + .data( root.descendants().slice(1) ) + .enter() + .append('path') + .attr("d", function(d) { + return "M" + d.y + "," + d.x + + "C" + (d.parent.y + 50) + "," + d.x + + " " + (d.parent.y + 150) + "," + d.parent.x // 50 and 150 are coordinates of inflexion, play with it to change links shape + + " " + d.parent.y + "," + d.parent.x; + }) + .style("fill", 'none') + .attr("stroke", '#ccc'); + + // Add a circle for each node. + var node = svg.selectAll("g") + .data(root.descendants()) + .enter() + .append("g") + .attr('class', 'node') + .attr("transform", function(d) { + return "translate(" + d.y + "," + d.x + ")" + }) + .append("circle") + .attr('class', 'nodeCircle') + .attr("r", 10) + .style("stroke", function(d) { + if (d.data.name === clickedItem) { + return "black"; + } else { + return ""; + } + }) + .style("fill", function(d) { + let type = sceneGraph[d.data.name].vehicleInfo.type; + if (type === 'ROOT') { + return "#ffffff"; + } else if (type === 'object') { + return "rgb(255,0,124)"; + } else if (type === 'frame') { + return "rgb(0, 255, 0)"; + } else if (type === 'node') { + return "rgb(0, 255, 255)"; + } + }) + // .attr("stroke", "black") + .style("stroke-width", 2) + .on("mouseover", mouseover) + .on("mousemove", mousemove) + .on("mouseleave", mouseleave) + .on('click', onClick); + + svg.selectAll(".node") + .append("text") + .attr("dx", function(d) { return d.children ? -12 : 12; }) + .attr("dy", 3) + .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) + .text(function(d) { + let id = d.data.name; + if (sceneGraph[id]) { + let name = sceneGraph[id].vehicleInfo.name; + if (name === 'ROOT') { return null; } + return name; + } + return id; + }); + + svg.selectAll(".node") + .append("text") + .attr("class", "distanceText") + .attr("visibility", "hidden") + .attr("dx", function(d) { + if (d.data.name === 'ROOT') { return 20; } + return d.children ? -6 : 12; + }) + .attr("dy", 20) + .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) + .text(function(d) { + return "0"; + }); + + if (clickedItem) { + if (typeof sceneGraph[clickedItem] !== 'undefined') { + showDistancesFrom(clickedItem, sceneGraph); + } else { + clickedItem = null; + } + } + }; + + function showDistancesFrom(id, sceneGraph) { + var svg = d3.select("#my_dataviz"); + + let otherIds = Object.keys(sceneGraph); + otherIds.splice(otherIds.indexOf(id), 1); + + getDistanceOneToMany(id, otherIds, function(distances) { + // console.log('received data', distances); + + // TODO: find max distance, min distance, affect style based on relative distance + let minDist = Number.MAX_SAFE_INTEGER; + let maxDist = Number.MIN_SAFE_INTEGER; + + for (let key in distances[id]) { + let distance = distances[id][key]; + if (distance > maxDist) { + maxDist = distance; + } + if (distance < minDist) { + minDist = distance; + } + } + + console.log('closest: ' + minDist); + console.log('furthest: ' + maxDist); + + let colorScale = d3.scaleLinear() + .domain([minDist, maxDist]) + .range([1, 0.5]); + + let sizeScale = d3.scaleLinear() + .domain([minDist, maxDist]) + .range([2, 1]); + + // // Create a scale: transform value in pixel + // var x = d3.scaleLinear() + // .domain([0, 100]) // This is the min and the max of the data: 0 to 100 if percentages + // .range([0, 400]); // This is the corresponding value I want in Pixel + // + // console.log(x(25)); + + svg.selectAll(".distanceText") + .attr("visibility", "visible") + .text(function (d2) { + let distance = distances[id][d2.data.name]; + if (typeof distance !== 'undefined') { + return (distance / 1000).toFixed(2) + 'm'; + } + return ""; + }); + + svg.selectAll('.node') + .attr('opacity', function (d2) { + let distance = distances[id][d2.data.name]; + if (typeof distance !== 'undefined') { + return colorScale(distance); + } + return 1; + }); + + svg.selectAll('.nodeCircle') + .attr("r", function (d2) { + let distance = distances[id][d2.data.name]; + if (typeof distance !== 'undefined') { + if (d2.data.name === clickedItem) { + return 20; + } + return 10 * sizeScale(distance); + } + return 10; + }); + }); + } + + function convertSceneGraphFormat(sceneGraph) { + /* + {"children":[{"name":"boss1","children":[{"name":"mister_a","colname":"level3"},{"name":"mister_b","colname":"level3"},{"name":"mister_c","colname":"level3"},{"name":"mister_d","colname":"level3"}],"colname":"level2"},{"name":"boss2","children":[{"name":"mister_e","colname":"level3"},{"name":"mister_f","colname":"level3"},{"name":"mister_g","colname":"level3"},{"name":"mister_h","colname":"level3"}],"colname":"level2"}],"name":"CEO"} + */ + + // let jsonData = { + // children: [] + // }; + let rootNode = sceneGraph['ROOT']; + return convertNodeToJson(sceneGraph, rootNode); + // return jsonData; + } + + function convertNodeToJson(sceneGraph, node) { + let convertedChildren = []; + node.children.forEach(function(id) { + let node = sceneGraph[id]; + if (!node) { return; } + convertedChildren.push(convertNodeToJson(sceneGraph, node)); + console.log(convertedChildren); + }); + return { + name: node.id, + children: convertedChildren + } + } + + function removeRotateXNodes(sceneGraph) { + // adjust all parents + Object.keys(sceneGraph).forEach(function(key) { + let node = sceneGraph[key]; + if (node.parent && node.parent.includes('rotateX')) { + let rotateX = sceneGraph[node.parent]; + node.parent = rotateX.parent; + } + }); + + // adjust all children + Object.keys(sceneGraph).forEach(function(key) { + let node = sceneGraph[key]; + + let rotateId = null; + if (node.children.some( function(childId) { + if (!childId) { return; } + if (childId.includes('rotateX')) { + rotateId = childId; + } + return rotateId; // keep iterating until you find a draggable frame + })) { + let rotateX = sceneGraph[rotateId]; + // node.children = rotateX.children; + if (rotateX) { + node.children.push.apply(node.children, rotateX.children); + } + } + }); + + // remove rotateX + let keysToRemove = []; + Object.keys(sceneGraph).forEach(function(key) { + if (key.includes('rotateX')) { + keysToRemove.push(key); + } + }); + keysToRemove.forEach(function(key) { + console.log('remove ' + key + ' from ' + sceneGraph[key].parent); + delete sceneGraph[key]; + }); + } + + function getWorldPosition(sceneNode, decimals) { + let numDecimals = (typeof decimals !== 'undefined') ? decimals : 3; + return { + x: (sceneNode.worldMatrix[12]/sceneNode.worldMatrix[15]).toFixed(decimals), + y: (sceneNode.worldMatrix[13]/sceneNode.worldMatrix[15]).toFixed(decimals), + z: (sceneNode.worldMatrix[14]/sceneNode.worldMatrix[15]).toFixed(decimals) + } + } + +})(window); From f164ee5e5153d6d1cf7306b1e0adb35a5a89d939 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 20 Nov 2020 13:16:13 -0500 Subject: [PATCH 009/658] ignores deactivated objects in scenegraph visualization --- .../scene-graph-visualizer/public/index.html | 3 --- .../public/visualization.js | 26 +++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/interfaces/scene-graph-visualizer/public/index.html b/interfaces/scene-graph-visualizer/public/index.html index cd29bcd6..ce9d28b9 100644 --- a/interfaces/scene-graph-visualizer/public/index.html +++ b/interfaces/scene-graph-visualizer/public/index.html @@ -88,10 +88,7 @@ socket.on('sceneGraph', function(e) { sceneGraphReference = e; - // console.log('received scene graph', sceneGraphReference); - // let svg = d3.select("#Viz_area"); visualize(sceneGraphReference); - // window.visualize2(sceneGraphReference); }); socket.emit('getSceneGraph', null); diff --git a/interfaces/scene-graph-visualizer/public/visualization.js b/interfaces/scene-graph-visualizer/public/visualization.js index b1adf28d..d68c5b4f 100644 --- a/interfaces/scene-graph-visualizer/public/visualization.js +++ b/interfaces/scene-graph-visualizer/public/visualization.js @@ -5,6 +5,7 @@ var clickedItem = null; function getData(sceneGraph) { console.log(sceneGraph); console.log('... processing ...'); + removeDeactivatedObjects(sceneGraph); removeRotateXNodes(sceneGraph); let data = convertSceneGraphFormat(sceneGraph); console.log(data); @@ -320,7 +321,7 @@ var clickedItem = null; let node = sceneGraph[id]; if (!node) { return; } convertedChildren.push(convertNodeToJson(sceneGraph, node)); - console.log(convertedChildren); + // console.log(convertedChildren); }); return { name: node.id, @@ -328,6 +329,27 @@ var clickedItem = null; } } + function removeDeactivatedObjects(sceneGraph) { + let keysToRemove = []; + Object.keys(sceneGraph).forEach(function(key) { + let node = sceneGraph[key]; + if (node.deactivated) { + keysToRemove.push(key); + if (node.parent) { + let parentNode = sceneGraph[node.parent]; + if (parentNode) { + let index = parentNode.children.indexOf(key); + parentNode.children.splice(index, 1); + } + } + } + }); + keysToRemove.forEach(function(key) { + console.log('remove deactivated ' + key + ' from ' + sceneGraph[key].parent); + delete sceneGraph[key]; + }); + } + function removeRotateXNodes(sceneGraph) { // adjust all parents Object.keys(sceneGraph).forEach(function(key) { @@ -366,7 +388,7 @@ var clickedItem = null; } }); keysToRemove.forEach(function(key) { - console.log('remove ' + key + ' from ' + sceneGraph[key].parent); + // console.log('remove ' + key + ' from ' + sceneGraph[key].parent); delete sceneGraph[key]; }); } From b636530e37ca429182a7f7548b0c0245d96f12d8 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Sat, 21 Nov 2020 12:45:27 -0500 Subject: [PATCH 010/658] retrieves the overall worldGraph from this network instead of just the sceneGraph of this server --- interfaces/scene-graph-visualizer/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interfaces/scene-graph-visualizer/index.js b/interfaces/scene-graph-visualizer/index.js index bbfecb51..a52444c1 100644 --- a/interfaces/scene-graph-visualizer/index.js +++ b/interfaces/scene-graph-visualizer/index.js @@ -86,7 +86,9 @@ if (exports.enabled) { }); socket.on('getSceneGraph', function() { - let sceneGraph = server.getSceneGraph(); + // let sceneGraph = server.getSceneGraph(); + let sceneGraph = server.getWorldGraph(); + // let stringifiedGraph = JSON.stringify(sceneGraph); // console.log(stringifiedGraph); socket.emit('sceneGraph', sceneGraph); From 462d1fc9c44a0ee01f4a17c8d9ea2db5cd89e446 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Mon, 30 Nov 2020 13:57:33 -0500 Subject: [PATCH 011/658] new function for getting visibleObjects on desktop --- content_scripts/desktopAdapter.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js index 6073c475..a9e0bdcc 100644 --- a/content_scripts/desktopAdapter.js +++ b/content_scripts/desktopAdapter.js @@ -591,11 +591,24 @@ var mRot = function(pitch, roll, yaw) { // trigger main update function after a 100ms delay, to synchronize with the approximate lag of the realityZone image processing // updateAfterDelay(visibleMatrixCopy, 100); - updateAfterDelay(visibleObjects, 100); + updateAfterDelay(getVisibleObjects(), 100); // repeat loop on next render requestAnimationFrame(update); } + + function getVisibleObjects() { + // render everything that has been localized + let tempVisibleObjects = {}; + + Object.keys(objects).forEach(function(objectKey) { + // todo: check if object.worldId matches a world that is currently loaded + + tempVisibleObjects[objectKey] = objects[objectKey].matrix; // right side here doesn't matter + }); + + return tempVisibleObjects; + } function updateAfterDelay(matrices, delayInMs) { setTimeout(function() { From ccdf87db425721b86f2f51d443d6eb6522fd50e1 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Mon, 30 Nov 2020 14:51:42 -0500 Subject: [PATCH 012/658] fix some desktopcamera methods that used visibleObject matrix to use sceneGraph instead --- content_scripts/desktopAdapter.js | 14 +++++++------- content_scripts/desktopCamera.js | 22 +++++++--------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js index a9e0bdcc..424c1119 100644 --- a/content_scripts/desktopAdapter.js +++ b/content_scripts/desktopAdapter.js @@ -356,13 +356,13 @@ var mRot = function(pitch, roll, yaw) { // realityEditor.gui.ar.draw.rotateX_desktopWorld = rotateX; // TODO ben: determine if rotoateX in sceneGraph needs to be updated like this - rotateX = [ - 1, 0, 0, 0, - 0, -1, 0, 0, - 0, 0, -1, 0, - 0, 0, 0, 1 - ]; - realityEditor.gui.ar.draw.rotateX = rotateX; + // rotateX = [ + // 1, 0, 0, 0, + // 0, -1, 0, 0, + // 0, 0, -1, 0, + // 0, 0, 0, 1 + // ]; + // realityEditor.gui.ar.draw.rotateX = rotateX; // desktopFrameTransform = mRot(0,0,90); // fixes the rotation but too late, not the resulting translation diff --git a/content_scripts/desktopCamera.js b/content_scripts/desktopCamera.js index 6a80f83b..739f5181 100644 --- a/content_scripts/desktopCamera.js +++ b/content_scripts/desktopCamera.js @@ -31,7 +31,6 @@ createNameSpace('realityEditor.device.desktopCamera'); window.webkitRequestAnimationFrame || function(cb) {setTimeout(cb, 17);}; // holds the most recent set of objectId/matrix pairs so that they can be rendered on the next frame - // var visibleObjects = {}; // hold the current camera position/rotation information, which can be updated with keyboard input var cameraTranslationMatrix = []; // these are defined here for memory allocation optimization, but updated each time @@ -273,12 +272,9 @@ createNameSpace('realityEditor.device.desktopCamera'); return; } - var mObj = realityEditor.gui.ar.draw.visibleObjects[objectKey]; - if (mObj) { - var objX = mObj[12] / mObj[15]; - var objY = mObj[13] / mObj[15]; - var objZ = mObj[14] / mObj[15]; - cameraTargetPosition = [objX, objY, objZ]; + var targetPosition = realityEditor.sceneGraph.getWorldPosition(objectKey); + if (targetPosition) { + cameraTargetPosition = [targetPosition.x, targetPosition.y, targetPosition.z]; isFollowingObjectTarget = true; } } @@ -647,15 +643,11 @@ createNameSpace('realityEditor.device.desktopCamera'); }; } - var mObj = realityEditor.gui.ar.draw.visibleObjects[targetObjectKey]; - var objX = mObj[12] / mObj[15]; - var objY = mObj[13] / mObj[15]; - var objZ = mObj[14] / mObj[15]; - + var targetPosition = realityEditor.sceneGraph.getWorldPosition(targetObjectKey); return { - x: objX, - y: objY, - z: objZ + x: targetPosition.x, + y: targetPosition.y, + z: targetPosition.z }; } From 0884b5a214e6a5706d4ad761ae42db1478e138dc Mon Sep 17 00:00:00 2001 From: James Hobin Date: Mon, 30 Nov 2020 17:30:39 -0500 Subject: [PATCH 013/658] fix camera pan/rotate directions and initial position for remote operator --- content_scripts/desktopCamera.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content_scripts/desktopCamera.js b/content_scripts/desktopCamera.js index 739f5181..aa1bb921 100644 --- a/content_scripts/desktopCamera.js +++ b/content_scripts/desktopCamera.js @@ -7,14 +7,14 @@ createNameSpace('realityEditor.device.desktopCamera'); (function() { - var cameraPosition = [735, -1575, -162]; //[1000, -500, 500]; + var cameraPosition = [1800, 7300, -5300]; //[735, -1575, -162]; //[1000, -500, 500]; var cameraTargetPosition = [0, 0, 0]; var previousTargetPosition = [0, 0, 0]; var currentDistanceToTarget = 500; var isFollowingObjectTarget = false; var closestObjectLog = null; - var targetOnLoad = 'kepwareBox4Qimhnuea3n6'; // TODO: load from localStorage the last targeted thing + var targetOnLoad = 'kepwareBox7Cjeujc54h5y'; //'kepwareBox4Qimhnuea3n6'; // TODO: load from localStorage the last targeted thing var DEBUG_SHOW_LOGGER = false; var DEBUG_REMOVE_KEYBOARD_CONTROLS = false; @@ -490,13 +490,13 @@ createNameSpace('realityEditor.device.desktopCamera'); // pan if shift held down if (keyStates[keyCodes.SHIFT] === 'down') { // STRAFE LEFT-RIGHT - let vector = scalarMultiply(horizontalVector, -1 * distancePanFactor * unprocessedMouseDX * getCameraPanSensitivity()); + let vector = scalarMultiply(horizontalVector, distancePanFactor * unprocessedMouseDX * getCameraPanSensitivity()); cameraTargetVelocity = add(cameraTargetVelocity, vector); cameraVelocity = add(cameraVelocity, vector); deselectTarget(); } else { // rotate otherwise - let vector = scalarMultiply(vCamX, -1 * cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDX); + let vector = scalarMultiply(vCamX, cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDX); cameraVelocity = add(cameraVelocity, vector); } @@ -507,13 +507,13 @@ createNameSpace('realityEditor.device.desktopCamera'); // pan if shift held down if (keyStates[keyCodes.SHIFT] === 'down') { // STRAFE UP-DOWN - let vector = scalarMultiply(verticalVector, -1 * distancePanFactor * unprocessedMouseDY * getCameraPanSensitivity()); + let vector = scalarMultiply(verticalVector, distancePanFactor * unprocessedMouseDY * getCameraPanSensitivity()); cameraTargetVelocity = add(cameraTargetVelocity, vector); cameraVelocity = add(cameraVelocity, vector); deselectTarget(); } else { // rotate otherwise - let vector = scalarMultiply(vCamY, -1 * cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDY); + let vector = scalarMultiply(vCamY, cameraSpeed * getCameraRotateSensitivity() * (2 * Math.PI * currentDistanceToTarget) * unprocessedMouseDY); cameraVelocity = add(cameraVelocity, vector); } From 521a59d06e28c0dc69b793204a2f31a71e21bf87 Mon Sep 17 00:00:00 2001 From: James Hobin Date: Wed, 2 Dec 2020 11:12:54 -0500 Subject: [PATCH 014/658] set isCameraOrientationFlipped env var to true --- content_scripts/desktopAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js index 424c1119..36f5bd39 100644 --- a/content_scripts/desktopAdapter.js +++ b/content_scripts/desktopAdapter.js @@ -85,6 +85,7 @@ var mRot = function(pitch, roll, yaw) { env.distanceScaleFactor = 10; env.localServerPort = 8080; // this would let it find world_local if it exists env.shouldCreateDesktopSocket = true; // this lets UDP messages get sent over socket instead + env.isCameraOrientationFlipped = true; // otherwise new tools and anchors get placed upside-down // default values that I may or may not need to invert: // env.providesOwnUpdateLoop: false, From ae1aff6b529e9eb81cc28104f68be2e1422a9f07 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Wed, 2 Dec 2020 13:59:59 -0500 Subject: [PATCH 015/658] remove scene-graph-visualizer hwinterface for now --- interfaces/scene-graph-visualizer/index.js | 108 ----- .../scene-graph-visualizer/public/index.html | 131 ------ .../public/reference.html | 255 ----------- .../public/visualization.js | 405 ------------------ 4 files changed, 899 deletions(-) delete mode 100644 interfaces/scene-graph-visualizer/index.js delete mode 100644 interfaces/scene-graph-visualizer/public/index.html delete mode 100644 interfaces/scene-graph-visualizer/public/reference.html delete mode 100644 interfaces/scene-graph-visualizer/public/visualization.js diff --git a/interfaces/scene-graph-visualizer/index.js b/interfaces/scene-graph-visualizer/index.js deleted file mode 100644 index a52444c1..00000000 --- a/interfaces/scene-graph-visualizer/index.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Created by Ben Reynolds on 11/9/20. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -/** - * Set to true to enable the hardware interface - **/ -var server = require('../../../../libraries/hardwareInterfaces'); -var utilities = require('../../../../libraries/utilities'); -var settings = server.loadHardwareInterface(__dirname); - -exports.enabled = settings("enabled"); -exports.configurable = true; // can be turned on/off/adjusted from the web frontend - -if (exports.enabled) { - - var express = require('express'); - var app = express(); - var bodyParser = require('body-parser'); - var cors = require('cors'); // Library for HTTP Cross-Origin-Resource-Sharing - // add the middleware - // use the CORS cross origin REST model - app.use(cors()); - // allow requests from all origins with '*'. TODO make it dependent on the local network. this is important for security - app.options('*', cors()); - app.use(express.static(__dirname + '/public')); - app.use(bodyParser.json()); - app.use(bodyParser.urlencoded({ extended: true })); - - startHTTPServer(8083); - - function startHTTPServer(port) { - - console.log('startHTTPServer on port ' + port + ' with dir: ' + __dirname); - - var http = require('http').Server(app); - var io = require('socket.io')(http); - - http.listen(port, function () { - console.log('started sceneGraph debugger on port ' + port); - - server.subscribeToMatrixStream(function(visibleObjects) { - io.emit('visibleObjects', visibleObjects); - }); - - server.subscribeToUDPMessages(function(msgContent) { - io.emit('udpMessage', msgContent); - }); - - function socketServer() { - - io.on('connection', function (socket) { - - console.log('connected to socket ' + socket.id); - io.emit('newValue', 1234); - - socket.on('/spatial/distance/oneToMany', function(msg) { - let msgContent = JSON.parse(msg); - // console.log('distance', msgContent); - let distances = server.getDistanceOneToMany(msgContent.id1, msgContent.ids); - console.log(distances); - - socket.emit('/spatial/distance/oneToMany', distances); - }); - - socket.on('newX', function (msg) { - var msgContent = JSON.parse(msg); - console.log('newX', msgContent); - }); - - socket.on('newY', function (msg) { - var msgContent = JSON.parse(msg); - console.log('newY', msgContent); - }); - - socket.on('getAllObjects', function() { - var objects = server.getAllObjects(); - socket.emit('allObjects', objects); - - var objectsOnOtherServers = server.getKnownObjects(); - socket.emit('allObjectsOnOtherServers', objectsOnOtherServers); - }); - - socket.on('getSceneGraph', function() { - // let sceneGraph = server.getSceneGraph(); - let sceneGraph = server.getWorldGraph(); - - // let stringifiedGraph = JSON.stringify(sceneGraph); - // console.log(stringifiedGraph); - socket.emit('sceneGraph', sceneGraph); - }); - - server.onSceneGraphUpdated(function() { - let sceneGraph = server.getSceneGraph(); - socket.emit('sceneGraph', sceneGraph); - }); - }); - } - - socketServer(); - }); - } - -} diff --git a/interfaces/scene-graph-visualizer/public/index.html b/interfaces/scene-graph-visualizer/public/index.html deleted file mode 100644 index ce9d28b9..00000000 --- a/interfaces/scene-graph-visualizer/public/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - Vuforia Spatial Edge Server - - - - - - - -
-
- Scene Graph Debugger -
- - - - -
-
- - - - diff --git a/interfaces/scene-graph-visualizer/public/reference.html b/interfaces/scene-graph-visualizer/public/reference.html deleted file mode 100644 index 428be23c..00000000 --- a/interfaces/scene-graph-visualizer/public/reference.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - Scene Graph Debugger - - - - - -
-
- Object Mover -
- -
- X Offset: -
-
- Y Offset: -
-
- Z Offset: -
-
- Rotation: -
- - - - - - - - - - -
- - - - diff --git a/interfaces/scene-graph-visualizer/public/visualization.js b/interfaces/scene-graph-visualizer/public/visualization.js deleted file mode 100644 index d68c5b4f..00000000 --- a/interfaces/scene-graph-visualizer/public/visualization.js +++ /dev/null @@ -1,405 +0,0 @@ -var clickedItem = null; - -(function(exports) { - - function getData(sceneGraph) { - console.log(sceneGraph); - console.log('... processing ...'); - removeDeactivatedObjects(sceneGraph); - removeRotateXNodes(sceneGraph); - let data = convertSceneGraphFormat(sceneGraph); - console.log(data); - return data; - } - - exports.visualize = function(sceneGraph) { - // reset SVG if it already existed - document.getElementById("my_dataviz").innerHTML = ''; - - let data = getData(sceneGraph); - - // set the dimensions and margins of the graph - var width = window.innerWidth - 40; - var height = window.innerHeight - 140; - - // append the svg object to the body of the page - var svg = d3.select("#my_dataviz") - .append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate(40,0)"); // bit of margin on the left = 40 - - // create a tooltip - var Tooltip = d3.select("#my_dataviz") - .append("div") - .style("opacity", 0) - .attr("class", "tooltip") - .style("background-color", "white") - .style("border", "solid") - .style("border-width", "2px") - .style("border-radius", "5px") - .style("padding", "5px") - .style("pointer-events", "none"); - - // Three function that change the tooltip when user hover / move / leave a cell - var mouseover = function(event, d) { - Tooltip - .style("opacity", 1) - .style("position", "absolute"); - d3.select(this) - .style("stroke", "black") - .style("opacity", 1) - - console.log('mouseover'); - - showDistancesFrom(d.data.name, sceneGraph); - }; - var mousemove = function(event, d) { - let sceneNode = sceneGraph[d.data.name]; - let pos = getWorldPosition(sceneNode, 0); - Tooltip - .html("Position:
(" + pos.x/1000 + "m, " + pos.y/1000 + "m, " + pos.z/1000 + "m)") - .style("left", event.clientX + 'px') //(d3.pointer(event)[0]+70) + "px") - .style("top", event.clientY + 'px'); //(d3.pointer(event)[1]) + "px") - - d3.select(this) - .attr("r", 20); - - console.log('mousemove'); - }; - var mouseleave = function(event, d) { - - Tooltip - .style("opacity", 0); - - if (clickedItem) { - if (clickedItem !== d.data.name) { - d3.select(this) - .style("stroke", "none") - .style("opacity", 0.8); - } //else { - // d3.select(this) - // .style('r', 20); - // } - showDistancesFrom(clickedItem, sceneGraph); - return; - } - - console.log('mouseleave'); - - svg.selectAll(".distanceText") - .attr("visibility", "hidden"); - - svg.selectAll('.node') - .attr('opacity', ''); - - svg.selectAll('.nodeCircle') - .style("stroke", function(d) { - if (d.data.name === clickedItem) { - return "black"; - } else { - return ""; - } - }) - .attr("r", 10) - }; - var onClick = function(event, d) { - console.log('clicked on ' + d.data.name); - if (clickedItem && clickedItem === d.data.name) { - clickedItem = null; - } else { - clickedItem = d.data.name; - } - }; - - let rightOffset = 300; - - // read json data - // d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_dendrogram.json", function(data) { - - var tree = d3.tree() - .size([height, width - rightOffset]); - - // Create the cluster layout: - var cluster = d3.cluster() - .size([height, width - rightOffset]); // 100 is the margin I will have on the right side - - // Give the data to this cluster layout: - var root = d3.hierarchy(data, function(d) { - return d.children; - }); - - if (useTree) { - tree(root); - } else { - cluster(root); - } - - // var scale = d3.scale.linear().domain([0, width - 300]).range([offset, width - 300]); - - // Add the links between nodes: - svg.selectAll('path') - .data( root.descendants().slice(1) ) - .enter() - .append('path') - .attr("d", function(d) { - return "M" + d.y + "," + d.x - + "C" + (d.parent.y + 50) + "," + d.x - + " " + (d.parent.y + 150) + "," + d.parent.x // 50 and 150 are coordinates of inflexion, play with it to change links shape - + " " + d.parent.y + "," + d.parent.x; - }) - .style("fill", 'none') - .attr("stroke", '#ccc'); - - // Add a circle for each node. - var node = svg.selectAll("g") - .data(root.descendants()) - .enter() - .append("g") - .attr('class', 'node') - .attr("transform", function(d) { - return "translate(" + d.y + "," + d.x + ")" - }) - .append("circle") - .attr('class', 'nodeCircle') - .attr("r", 10) - .style("stroke", function(d) { - if (d.data.name === clickedItem) { - return "black"; - } else { - return ""; - } - }) - .style("fill", function(d) { - let type = sceneGraph[d.data.name].vehicleInfo.type; - if (type === 'ROOT') { - return "#ffffff"; - } else if (type === 'object') { - return "rgb(255,0,124)"; - } else if (type === 'frame') { - return "rgb(0, 255, 0)"; - } else if (type === 'node') { - return "rgb(0, 255, 255)"; - } - }) - // .attr("stroke", "black") - .style("stroke-width", 2) - .on("mouseover", mouseover) - .on("mousemove", mousemove) - .on("mouseleave", mouseleave) - .on('click', onClick); - - svg.selectAll(".node") - .append("text") - .attr("dx", function(d) { return d.children ? -12 : 12; }) - .attr("dy", 3) - .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) - .text(function(d) { - let id = d.data.name; - if (sceneGraph[id]) { - let name = sceneGraph[id].vehicleInfo.name; - if (name === 'ROOT') { return null; } - return name; - } - return id; - }); - - svg.selectAll(".node") - .append("text") - .attr("class", "distanceText") - .attr("visibility", "hidden") - .attr("dx", function(d) { - if (d.data.name === 'ROOT') { return 20; } - return d.children ? -6 : 12; - }) - .attr("dy", 20) - .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) - .text(function(d) { - return "0"; - }); - - if (clickedItem) { - if (typeof sceneGraph[clickedItem] !== 'undefined') { - showDistancesFrom(clickedItem, sceneGraph); - } else { - clickedItem = null; - } - } - }; - - function showDistancesFrom(id, sceneGraph) { - var svg = d3.select("#my_dataviz"); - - let otherIds = Object.keys(sceneGraph); - otherIds.splice(otherIds.indexOf(id), 1); - - getDistanceOneToMany(id, otherIds, function(distances) { - // console.log('received data', distances); - - // TODO: find max distance, min distance, affect style based on relative distance - let minDist = Number.MAX_SAFE_INTEGER; - let maxDist = Number.MIN_SAFE_INTEGER; - - for (let key in distances[id]) { - let distance = distances[id][key]; - if (distance > maxDist) { - maxDist = distance; - } - if (distance < minDist) { - minDist = distance; - } - } - - console.log('closest: ' + minDist); - console.log('furthest: ' + maxDist); - - let colorScale = d3.scaleLinear() - .domain([minDist, maxDist]) - .range([1, 0.5]); - - let sizeScale = d3.scaleLinear() - .domain([minDist, maxDist]) - .range([2, 1]); - - // // Create a scale: transform value in pixel - // var x = d3.scaleLinear() - // .domain([0, 100]) // This is the min and the max of the data: 0 to 100 if percentages - // .range([0, 400]); // This is the corresponding value I want in Pixel - // - // console.log(x(25)); - - svg.selectAll(".distanceText") - .attr("visibility", "visible") - .text(function (d2) { - let distance = distances[id][d2.data.name]; - if (typeof distance !== 'undefined') { - return (distance / 1000).toFixed(2) + 'm'; - } - return ""; - }); - - svg.selectAll('.node') - .attr('opacity', function (d2) { - let distance = distances[id][d2.data.name]; - if (typeof distance !== 'undefined') { - return colorScale(distance); - } - return 1; - }); - - svg.selectAll('.nodeCircle') - .attr("r", function (d2) { - let distance = distances[id][d2.data.name]; - if (typeof distance !== 'undefined') { - if (d2.data.name === clickedItem) { - return 20; - } - return 10 * sizeScale(distance); - } - return 10; - }); - }); - } - - function convertSceneGraphFormat(sceneGraph) { - /* - {"children":[{"name":"boss1","children":[{"name":"mister_a","colname":"level3"},{"name":"mister_b","colname":"level3"},{"name":"mister_c","colname":"level3"},{"name":"mister_d","colname":"level3"}],"colname":"level2"},{"name":"boss2","children":[{"name":"mister_e","colname":"level3"},{"name":"mister_f","colname":"level3"},{"name":"mister_g","colname":"level3"},{"name":"mister_h","colname":"level3"}],"colname":"level2"}],"name":"CEO"} - */ - - // let jsonData = { - // children: [] - // }; - let rootNode = sceneGraph['ROOT']; - return convertNodeToJson(sceneGraph, rootNode); - // return jsonData; - } - - function convertNodeToJson(sceneGraph, node) { - let convertedChildren = []; - node.children.forEach(function(id) { - let node = sceneGraph[id]; - if (!node) { return; } - convertedChildren.push(convertNodeToJson(sceneGraph, node)); - // console.log(convertedChildren); - }); - return { - name: node.id, - children: convertedChildren - } - } - - function removeDeactivatedObjects(sceneGraph) { - let keysToRemove = []; - Object.keys(sceneGraph).forEach(function(key) { - let node = sceneGraph[key]; - if (node.deactivated) { - keysToRemove.push(key); - if (node.parent) { - let parentNode = sceneGraph[node.parent]; - if (parentNode) { - let index = parentNode.children.indexOf(key); - parentNode.children.splice(index, 1); - } - } - } - }); - keysToRemove.forEach(function(key) { - console.log('remove deactivated ' + key + ' from ' + sceneGraph[key].parent); - delete sceneGraph[key]; - }); - } - - function removeRotateXNodes(sceneGraph) { - // adjust all parents - Object.keys(sceneGraph).forEach(function(key) { - let node = sceneGraph[key]; - if (node.parent && node.parent.includes('rotateX')) { - let rotateX = sceneGraph[node.parent]; - node.parent = rotateX.parent; - } - }); - - // adjust all children - Object.keys(sceneGraph).forEach(function(key) { - let node = sceneGraph[key]; - - let rotateId = null; - if (node.children.some( function(childId) { - if (!childId) { return; } - if (childId.includes('rotateX')) { - rotateId = childId; - } - return rotateId; // keep iterating until you find a draggable frame - })) { - let rotateX = sceneGraph[rotateId]; - // node.children = rotateX.children; - if (rotateX) { - node.children.push.apply(node.children, rotateX.children); - } - } - }); - - // remove rotateX - let keysToRemove = []; - Object.keys(sceneGraph).forEach(function(key) { - if (key.includes('rotateX')) { - keysToRemove.push(key); - } - }); - keysToRemove.forEach(function(key) { - // console.log('remove ' + key + ' from ' + sceneGraph[key].parent); - delete sceneGraph[key]; - }); - } - - function getWorldPosition(sceneNode, decimals) { - let numDecimals = (typeof decimals !== 'undefined') ? decimals : 3; - return { - x: (sceneNode.worldMatrix[12]/sceneNode.worldMatrix[15]).toFixed(decimals), - y: (sceneNode.worldMatrix[13]/sceneNode.worldMatrix[15]).toFixed(decimals), - z: (sceneNode.worldMatrix[14]/sceneNode.worldMatrix[15]).toFixed(decimals) - } - } - -})(window); From 2f2f1d8031fb7f8264869d25a881b0ee90f584a5 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Wed, 2 Dec 2020 15:04:35 -0500 Subject: [PATCH 016/658] lots of cleanup with linter to identify and remove now-unused variables and functions --- .eslintrc.js | 32 ++++ content_scripts/desktopAdapter.js | 261 ++++------------------------- content_scripts/desktopCamera.js | 196 +++------------------- content_scripts/desktopRenderer.js | 121 ------------- content_scripts/desktopStats.js | 2 +- 5 files changed, 90 insertions(+), 522 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..fc465061 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "globals": { + "Stats": "readonly", + + "realityEditor": "writable", + "createNameSpace": "writable", + "globalStates": "writable", + "objects": "writable" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "no-prototype-builtins": "off", + "no-redeclare": [ + "error", + {"builtinGlobals": false} + ], + "no-unused-vars": [ + "error", + { + "varsIgnorePattern": "^_", + "argsIgnorePattern": "^_", + }, + ], + } +}; diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js index 36f5bd39..c290c176 100644 --- a/content_scripts/desktopAdapter.js +++ b/content_scripts/desktopAdapter.js @@ -6,18 +6,9 @@ createNameSpace('realityEditor.device.desktopAdapter'); * waits for a connection from a mobile editor that will stream matrices here */ -var mRot = function(pitch, roll, yaw) { - return realityEditor.gui.ar.utilities.getMatrixFromQuaternion(realityEditor.gui.ar.utilities.getQuaternionFromPitchRollYaw(pitch * Math.PI / 180, roll * Math.PI / 180, yaw * Math.PI / 180)); -}; - (function(exports) { // Automatically connect to all discovered reality zones const AUTO_ZONE_CONNECT = true; - - /** - * @type {number} - the handle for the setInterval for mobile editors to connect to desktops - */ - var broadcastInterval; /** * @type {boolean} - when paused, desktops ignore matrices received from mobile editors and use their own @@ -76,38 +67,28 @@ var mRot = function(pitch, roll, yaw) { * initialize the desktop adapter only if we are running on a desktop environment */ function initService() { - if (isDesktop()) { - env.requiresMouseEvents = true; // this fixes touch events to become mouse events - env.supportsDistanceFading = false; // this prevents things from disappearing when the camera zooms out - env.ignoresFreezeButton = true; // this - env.shouldDisplayLogicMenuModally = true; - env.lineWidthMultiplier = 5; - env.distanceScaleFactor = 10; - env.localServerPort = 8080; // this would let it find world_local if it exists - env.shouldCreateDesktopSocket = true; // this lets UDP messages get sent over socket instead - env.isCameraOrientationFlipped = true; // otherwise new tools and anchors get placed upside-down - - // default values that I may or may not need to invert: - // env.providesOwnUpdateLoop: false, - // shouldBroadcastUpdateObjectMatrix: false, - // doWorldObjectsRequireCameraTransform: false, - // distanceRequiresCameraTransform: false, - } else { - - // // TODO: replace this with a better version that isn't reliant on editor being open at same time - // // for NON desktop editors, they should broadcast their visible object matrices when enabled - // realityEditor.gui.ar.draw.addUpdateListener(function(visibleObjects) { - // if (globalStates.matrixBroadcastEnabled) { - // broadcastMatrices(visibleObjects); - // } - // }); - - return; - } + // by including this check, we can tolerate compiling this add-on into the app without breaking everything + // (ideally this add-on should only be added to a "desktop" server but this should effectively ignore it on mobile) + if (!isDesktop()) { return; } + + // Set the correct environment variables so that this add-on changes the app to run in desktop mode + env.requiresMouseEvents = true; // this fixes touch events to become mouse events + env.supportsDistanceFading = false; // this prevents things from disappearing when the camera zooms out + env.ignoresFreezeButton = true; // no need to "freeze the camera" on desktop + env.shouldDisplayLogicMenuModally = true; // affects appearance of crafting board + env.lineWidthMultiplier = 5; // makes links thicker (more visible) + env.distanceScaleFactor = 10; // makes distance-based interactions work at further distances than mobile + env.localServerPort = 8080; // this would let it find world_local if it exists (but it probably doesn't exist) + env.shouldCreateDesktopSocket = true; // this lets UDP messages get sent over socket instead + env.isCameraOrientationFlipped = true; // otherwise new tools and anchors get placed upside-down + + // default values that I may or may not need to invert: + // shouldBroadcastUpdateObjectMatrix: false, restyleForDesktop(); modifyGlobalNamespace(); + // TODO realtime interactions between remote operator and AR clients need to be re-tested and possibly fixed setTimeout(function() { addSocketListeners(); // HACK. this needs to happen after realtime module finishes loading }, 100); @@ -115,9 +96,6 @@ var mRot = function(pitch, roll, yaw) { addZoneDiscoveredListener(); addZoneControlListener(); - // TODO: is this really the best way to do this? data should come from server, not browser storage - // visibleObjects = JSON.parse(window.localStorage.getItem('realityEditor.desktopAdapter.savedMatrices') || '{}'); - savedZoneSocketIPs = JSON.parse(window.localStorage.getItem('realityEditor.desktopAdapter.savedZoneSocketIPs') || '[]'); // Reset savedZoneSocketIPs so that they are only persisted on idle refresh @@ -135,7 +113,7 @@ var mRot = function(pitch, roll, yaw) { } var desktopProjectionMatrix = projectionMatrixFrom(25, -window.innerWidth / window.innerHeight, 0.1, 300000); - console.log('desktop matrix', desktopProjectionMatrix); + console.log('calculated desktop projection matrix:', desktopProjectionMatrix); // noinspection JSSuspiciousNameCombination globalStates.height = window.innerWidth; @@ -235,10 +213,6 @@ var mRot = function(pitch, roll, yaw) { } }); } - - let defaultPrimaryZoneIP = '10.10.10.105'; - let primaryZoneIP = defaultPrimaryZoneIP; - let isUsingCustomPrimaryIP = false; /** * Adjust visuals for desktop rendering -> set background color and add "Waiting for Connection..." indicator @@ -262,26 +236,21 @@ var mRot = function(pitch, roll, yaw) { // add setting to type in IP address of primary zone realityEditor.gui.settings.addToggleWithText('Primary Zone URL', 'IP address of first zone (e.g. 10.10.10.105)', 'primaryZoneIP' ,'../../../svg/download.svg', false, '10.10.10.105', - function(toggleValue, textValue) { + function(_toggleValue, _textValue) { // isUsingCustomPrimaryIP = toggleValue; - // if (toggleValue && textValue.length > 0) { - // primaryZoneIP = textValue; - // } else { - // primaryZoneIP = defaultPrimaryZoneIP; - // } - // console.log('primaryZoneIP = ' + primaryZoneIP); }, function(textValue) { console.log('zone text was set to ' + textValue); - // if (isUsingCustomPrimaryIP) { - // primaryZoneIP = textValue; - // } - // console.log('primaryZoneIP = ' + primaryZoneIP); }, - true);//.setValue(!window.location.href.includes('127.0.0.1')); // default value is based on the current source + true); } - + + /** + * Images coming from a reality zone at this IP address will be rendered into background. + * Images coming from another IP address will be treated as a "secondary" zone + * @return {string|*} + */ function getPrimaryZoneIP() { if (realityEditor.gui.settings.toggleStates.primaryZoneIP && realityEditor.gui.settings.toggleStates.primaryZoneIPText) { return realityEditor.gui.settings.toggleStates.primaryZoneIPText; @@ -328,35 +297,10 @@ var mRot = function(pitch, roll, yaw) { } }; - // polyfill webkit functions on Chrome browser - // if (typeof window.webkitConvertPointFromPageToNode === 'undefined') { - // console.log('Polyfilling webkitConvertPointFromPageToNode for this browser'); - // - // polyfillWebkitConvertPointFromPageToNode(); - // - // var ssEl = document.createElement('style'), - // css = '.or{position:absolute;opacity:0;height:33.333%;width:33.333%;top:0;left:0}.or.r-2{left:33.333%}.or.r-3{left:66.666%}.or.r-4{top:33.333%}.or.r-5{top:33.333%;left:33.333%}.or.r-6{top:33.333%;left:66.666%}.or.r-7{top:66.666%}.or.r-8{top:66.666%;left:33.333%}.or.r-9{top:66.666%;left:66.666%}'; - // ssEl.type = 'text/css'; - // (ssEl.styleSheet) ? - // ssEl.styleSheet.cssText = css : - // ssEl.appendChild(document.createTextNode(css)); - // document.getElementsByTagName('head')[0].appendChild(ssEl); - // } - - // pocketBegin = [4809.935578545477, -21.448650897337046, 0.4699633267584691, 0.46996614495062317, -44.91500625468903, 4866.661791176597, 0.11589206604418023, 0.11589146054651356, 1868.0710976511762, 151.4880583152451, 3.796594149786673, 3.7967717726783503, 253912.5079441294, -209710.60820716852, 4880.993541501766, 4881.118759195814]; - - // realityEditor.gui.ar.draw.correctedCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); - - // realityEditor.gui.ar.draw.groundPlaneMatrix = realityEditor.gui.ar.draw.correctedCameraMatrix; // ground plane just points to camera matrix on desktop - - // rotateX = mRot(0,0,-90); - - // rotateX = mRot(0,-90,0); + // don't need to polyfill webkit functions for Chrome here because it is already polyfilled in the userinterface - // rotateX = mFlipYZ; - // realityEditor.gui.ar.draw.rotateX_desktopWorld = rotateX; - - // TODO ben: determine if rotoateX in sceneGraph needs to be updated like this + // TODO: unsure if env.isCameraOrientationFlipped was only necessary because rotateX needs to be different on desktop.. + // investigate this further to potentially simplify calculations // rotateX = [ // 1, 0, 0, 0, // 0, -1, 0, 0, @@ -364,80 +308,7 @@ var mRot = function(pitch, roll, yaw) { // 0, 0, 0, 1 // ]; // realityEditor.gui.ar.draw.rotateX = rotateX; - - // desktopFrameTransform = mRot(0,0,90); // fixes the rotation but too late, not the resulting translation - - // desktopFrameTransform = mFlipYZ; - } - - // /** - // * Based off of https://gist.github.com/Yaffle/1145197 with modifications to - // * support more complex matrices - // */ - // function polyfillWebkitConvertPointFromPageToNode() { - // const identity = new DOMMatrix([ - // 1, 0, 0, 0, - // 0, 1, 0, 0, - // 0, 0, 1, 0, - // 0, 0, 0, 1 - // ]); - // - // if (!window.WebKitPoint) { - // window.WebKitPoint = DOMPoint; - // } - // - // function getTransformationMatrix(element) { - // var transformationMatrix = identity; - // var x = element; - // - // while (x !== undefined && x !== x.ownerDocument.documentElement) { - // var computedStyle = window.getComputedStyle(x); - // var transform = computedStyle.transform || "none"; - // var c = transform === "none" ? identity : new DOMMatrix(transform); - // - // transformationMatrix = c.multiply(transformationMatrix); - // x = x.parentNode; - // } - // - // // Normalize current matrix to have m44=1 (w = 1). Math does not work - // // otherwise because nothing knows how to scale based on w - // let baseArr = transformationMatrix.toFloat64Array(); - // baseArr = baseArr.map(b => b / baseArr[15]); - // transformationMatrix = new DOMMatrix(baseArr); - // - // var w = element.offsetWidth; - // var h = element.offsetHeight; - // var i = 4; - // var left = +Infinity; - // var top = +Infinity; - // while (--i >= 0) { - // var p = transformationMatrix.transformPoint(new DOMPoint(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, 0)); - // if (p.x < left) { - // left = p.x; - // } - // if (p.y < top) { - // top = p.y; - // } - // } - // var rect = element.getBoundingClientRect(); - // transformationMatrix = identity.translate(window.pageXOffset + rect.left - left, window.pageYOffset + rect.top - top, 0).multiply(transformationMatrix); - // return transformationMatrix; - // } - // - // window.convertPointFromPageToNode = window.webkitConvertPointFromPageToNode = function (element, point) { - // let mati = getTransformationMatrix(element).inverse(); - // // This involves a lot of math, sorry. - // // Given $v = M^{-1}p$ we have p.x, p.y, p.w, M^{-1}, and know that v.z - // // should be equal to 0. - // // Solving for p.z we get the following: - // let projectedZ = -(mati.m13 * point.x + mati.m23 * point.y + mati.m43) / mati.m33; - // return mati.transformPoint(new DOMPoint(point.x, point.y, projectedZ)); - // }; - // - // window.convertPointFromNodeToPage = function (element, point) { - // return getTransformationMatrix(element).transformPoint(point); - // }; - // } + } /** * Adds socket.io listeners for matrix streams and UDP messages necessary to setup editor without mobile environment @@ -485,7 +356,7 @@ var mRot = function(pitch, roll, yaw) { callbackHandler.triggerCallbacks('objectMatrixDiscovered', {objectKey: msgData.objectKey}); } - // TODO: set sceneGraph localMatrix to msgData.newValue + // TODO: set sceneGraph localMatrix to msgData.newValue? // var rotatedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(msgData.newValue); // object.matrix = rotatedObjectMatrix; // visibleObjects[msgData.objectKey] = rotatedObjectMatrix; @@ -502,7 +373,7 @@ var mRot = function(pitch, roll, yaw) { realityEditor.network.addHeartbeatObject(msgContent); } - // TODO: understand what this is doing and if it's still necessary + // TODO: I believe this can be removed because it was an old method for synchronizing the desktop camera with an AR client's camera if (!currentConnectedDeviceIP) { if (typeof msgContent.projectionMatrix !== 'undefined') { if (typeof msgContent.ip !== 'undefined' && typeof msgContent.port !== 'undefined') { @@ -616,16 +487,6 @@ var mRot = function(pitch, roll, yaw) { realityEditor.gui.ar.draw.update(matrices); }, delayInMs); } - - /** - * Sends the current matrix set to all connected desktop editors - * @param {Object.>} visibleObjects - */ - function broadcastMatrices(visibleObjects) { - var eventName = '/matrix/visibleObjects'; - var messageBody = {visibleObjects: visibleObjects, ip: window.location.hostname, port: window.location.port}; - realityEditor.network.realtime.sendMessageToSocketSet('desktopEditors', eventName, messageBody); - } /** * Creates a switch that, when activated, begins broadcasting UDP messages @@ -859,39 +720,6 @@ var mRot = function(pitch, roll, yaw) { realityEditor.app.getUDPMessages('realityEditor.app.callbacks.receivedUDPMessage'); } - /** - * Broadcasts this editor's projection matrix to all desktop editors, so that they can use it. - * Checks if there are any objects from new servers, and establishes a socket connection to their desktop clients. - * Should be called on mobile editors only. - */ - function broadcastEditorInformation() { - - realityEditor.app.sendUDPMessage({ - action: 'mobileEditorDiscovered', - ip: window.location.hostname, - port: parseInt(window.location.port), - // uuid: globalStates.tempUuid - projectionMatrix: globalStates.projectionMatrix, - realProjectionMatrix: globalStates.realProjectionMatrix - }); - - var ipList = []; - realityEditor.forEachObject(function(object, _objectKey) { - if (ipList.indexOf(object.ip) === -1) { - ipList.push(object.ip); - } - }); - - var desktopEditorPort = 8081; - ipList.forEach(function(ip) { - var potentialDesktopEditorAddress = 'http://' + ip + ':' + desktopEditorPort; - var socketsIps = realityEditor.network.realtime.getSocketIPsForSet('desktopEditors'); - if (socketsIps.indexOf(potentialDesktopEditorAddress) < 0) { - realityEditor.network.realtime.createSocketInSet('desktopEditors', potentialDesktopEditorAddress); - } - }); - } - function resetIdleTimeout() { if (idleTimeout) { clearTimeout(idleTimeout); @@ -910,29 +738,6 @@ var mRot = function(pitch, roll, yaw) { window.location.reload(); } - // exports.updateObjectMatrix = function(objectKey, pitch, roll, yaw) { - // var object = realityEditor.getObject(objectKey); - // var rotatedObjectMatrix = realityEditor.gui.ar.utilities.copyMatrix(object.matrix); - // - // // var rotatedObjectMatrix = []; - // var rotation3d = [ - // 1, 0, 0, 0, - // 0, 0, -1, 0, - // 0, -1, 0, 0, - // 0, 0, 0, 1 - // ]; - // realityEditor.gui.ar.utilities.multiplyMatrix(rotation3d, object.matrix, rotatedObjectMatrix); - // var rotation2d = mRot(pitch, roll, yaw); - // realityEditor.gui.ar.utilities.multiplyMatrix(rotation2d, rotatedObjectMatrix, rotatedObjectMatrix); - // var oldZ = rotatedObjectMatrix[14]; - // rotatedObjectMatrix[14] = -rotatedObjectMatrix[13]; - // rotatedObjectMatrix[13] = -oldZ; - // - // console.log('object ' + objectKey + 'is at (' + rotatedObjectMatrix[12] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[13] / rotatedObjectMatrix[15] + ', ' + rotatedObjectMatrix[14] / rotatedObjectMatrix[15] + ')'); - // - // visibleObjects[objectKey] = rotatedObjectMatrix; - // }; - // Currently unused exports.registerCallback = registerCallback; diff --git a/content_scripts/desktopCamera.js b/content_scripts/desktopCamera.js index aa1bb921..fbf63967 100644 --- a/content_scripts/desktopCamera.js +++ b/content_scripts/desktopCamera.js @@ -12,80 +12,61 @@ createNameSpace('realityEditor.device.desktopCamera'); var previousTargetPosition = [0, 0, 0]; var currentDistanceToTarget = 500; var isFollowingObjectTarget = false; - var closestObjectLog = null; - var targetOnLoad = 'kepwareBox7Cjeujc54h5y'; //'kepwareBox4Qimhnuea3n6'; // TODO: load from localStorage the last targeted thing + // this is the final camera matrix that will be computed from lookAt(cameraPosition, cameraTargetPosition) + var destinationCameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + + var targetOnLoad = window.localStorage.getItem('selectedObjectKey'); var DEBUG_SHOW_LOGGER = false; var DEBUG_REMOVE_KEYBOARD_CONTROLS = false; var DEBUG_PREVENT_CAMERA_SINGULARITIES = false; + var closestObjectLog = null; // if DEBUG_SHOW_LOGGER, this will be a text field /** * @type {Dropdown} - DOM element to choose which object to target for the camera */ var objectDropdown; - var selectedObjectKey = null; //null; //'closestObject'; + var selectedObjectKey = null; // polyfill for requestAnimationFrame to provide a smooth update loop var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function(cb) {setTimeout(cb, 17);}; - // holds the most recent set of objectId/matrix pairs so that they can be rendered on the next frame - - // hold the current camera position/rotation information, which can be updated with keyboard input - var cameraTranslationMatrix = []; // these are defined here for memory allocation optimization, but updated each time - var cameraRotationMatrix = []; - - var cameraX = 200; //0; - var cameraY = -1170; //0; - var cameraZ = 2230; //7000; - - var cameraVelocityX = 0; - var cameraVelocityY = 0; - var cameraVelocityZ = 0; - - var cameraPitch = 0; - var cameraRoll = 0; - var cameraYaw = 0; - - var cameraVelocityPitch = 0; - var cameraVelocityRoll = 0; - var cameraVelocityYaw = 0; + /* ---------- how fast the camera will pan and rotate each frame --------- */ + // affects keyboard and mouse controls + var cameraSpeed = 0.0001; - // how fast the camera will pan and rotate each frame (units are scaled for vuforia modelview matrix) - var cameraSpeed = 0.0001; // 0.01; + // affects keyboard controls var keyboardSpeedMultiplier = { translation: 50000, rotation: 10, scale: 250000 - }; //1000; - - let scrollWheelMultiplier = 10000; + }; + // affects mouse controls + let scrollWheelMultiplier = 10000; var mouseSpeedMultiplier = { translation: 250, rotation: .0015, scale: 350 }; + /* ----------------------------------------------------------------------- */ + /* ------------- keep track of mouse and scroll wheel movement ----------- */ var firstX = 0; var firstY = 0; var lastX = 0; var lastY = 0; - // var firstMouse = true; var isMouseDown = false; - // var sensitivity = 1; var unprocessedMouseMovements = []; - let unprocessedScrollDY = 0; let unprocessedMouseDX = 0; let unprocessedMouseDY = 0; + // this is used for 6D mouse controls var mouseMovement = {}; - - var cameraUpVector = [0, 1, 0]; - - var areControlsInverted = true; + /* ----------------------------------------------------------------------- */ /** * Enum mapping readable keyboard names to their keyCode @@ -114,19 +95,6 @@ createNameSpace('realityEditor.device.desktopCamera'); */ var keyStates = {}; - /** - * for cameraModes.CLOSEST_OBJECT - holds how much the camera has rotated while each objectId was the closest - * @type {Object.} - */ - var rotations = {}; - - var speedMultipliers = { - translation: -100, - rotation: 0.01 - }; - - var destinationCameraMatrix = []; //JSON.parse("[0.9970872369217663,-0.0195192707153495,-0.0737295058877905,0,0,0.9666966751096482,-0.2559248685297132,0,0.076269534990826,0.2551794200218579,0.9638809167265376,0,9.292587708200172e-166,2.2737367544323196e-13,-4789.705780105794,1]"); //[]; - /** * Public init method to enable rendering if isDesktop */ @@ -176,11 +144,6 @@ createNameSpace('realityEditor.device.desktopCamera'); closestObjectLog.style.color = 'cyan'; document.body.appendChild(closestObjectLog); } - - // setInterval(function() { - // var closestObjectKey = realityEditor.gui.ar.getClosestObject()[0]; - // closestObjectLog.innerText = closestObjectKey; - // }, 1000); } function addSensitivitySlidersToMenu() { @@ -224,17 +187,11 @@ createNameSpace('realityEditor.device.desktopCamera'); objectDropdown.addSelectable('origin', 'World Origin'); - // objectDropdown.addSelectable('null', 'No Target'); - // objectDropdown.addSelectable('inverted', 'Inverted'); - // objectDropdown.addSelectable('classic', 'Classic'); - // objectDropdown.addSelectable('closestObject', 'Closest Object'); // objectDropdown.addSelectable('floatingPoint2000', 'Floating Point (2m)'); // objectDropdown.addSelectable('floatingPoint5000', 'Floating Point (5m)'); // objectDropdown.addSelectable('floatingPoint10000', 'Floating Point (10m)'); // objectDropdown.addSelectable('floatingPoint20000', 'Floating Point (20m)'); - // objectDropdown.setText('Selected: Inverted', true); - Object.keys(objects).forEach(function(objectKey) { tryAddingObjectToDropdown(objectKey); }); @@ -295,11 +252,8 @@ createNameSpace('realityEditor.device.desktopCamera'); selectedObjectKey = objectKey; setTargetPositionToObject(objectKey); previousTargetPosition = [cameraTargetPosition[0], cameraTargetPosition[1], cameraTargetPosition[2]]; - // if (objectKey === 'null' || objectKey === 'inverted') { - // areControlsInverted = objectKey === 'inverted'; - // objectKey = null; - // // resetCamera(); // TODO: instead of reset, calculate the position+rotation needed to maintain visual consistency - // } + + window.localStorage.setItem('selectedObjectKey', objectKey); } function onObjectExpandedChanged(_isExpanded) { @@ -371,10 +325,6 @@ createNameSpace('realityEditor.device.desktopCamera'); var cv = cameraTargetPosition; //[objX, objY, objZ]; var uv = [0, 1, 0]; - // var forwardVector = normalize(add(ev, negate(cv))); // vector from the camera to the center point - // var horizontalVector = normalize(crossProduct(uv, forwardVector)); // a "right" vector, orthogonal to n and the lookup vector - // var verticalVector = crossProduct(forwardVector, horizontalVector); // resulting orthogonal vector to n and u, as the up vector isn't necessarily one anymore - currentDistanceToTarget = magnitude(add(ev, negate(cv))); // console.log(distanceToCenter); // var upRotationVelocity = 0; @@ -382,11 +332,7 @@ createNameSpace('realityEditor.device.desktopCamera'); var mCamera = destinationCameraMatrix; // translation is based on what direction you're facing, var vCamX = normalize([mCamera[0], mCamera[4], mCamera[8]]); var vCamY = normalize([mCamera[1], mCamera[5], mCamera[9]]); - var vCamZ = normalize([mCamera[2], mCamera[6], mCamera[10]]); - - // var strafeSpeedMultiplier = 25; - // var zoomSpeedMultiplier = 100; - // var threshold = 0.1; + var _vCamZ = normalize([mCamera[2], mCamera[6], mCamera[10]]); cameraSpeed = 0.001; // 0.01; @@ -484,7 +430,7 @@ createNameSpace('realityEditor.device.desktopCamera'); unprocessedScrollDY = 0; } - let distancePanFactor = currentDistanceToTarget / 1000; // speed when 1 meter units away, scales up w/ distance + let distancePanFactor = Math.max(1, currentDistanceToTarget / 1000); // speed when 1 meter units away, scales up w/ distance if (unprocessedMouseDX !== 0) { // pan if shift held down @@ -594,11 +540,6 @@ createNameSpace('realityEditor.device.desktopCamera'); realityEditor.sceneGraph.setCameraPosition(newCameraMatrix); // TODO ben: make sure groundplane matrix works on desktop - // var rotatedGroundPlaneMatrix = []; - //var rotation3d = makeRotationY(Math.PI/2); - // realityEditor.gui.ar.utilities.multiplyMatrix(window.gpMat, realityEditor.gui.ar.draw.correctedCameraMatrix, rotatedGroundPlaneMatrix); - // - // realityEditor.gui.ar.draw.groundPlaneMatrix = rotatedGroundPlaneMatrix; } function getBaseLog(x, y) { @@ -628,29 +569,6 @@ createNameSpace('realityEditor.device.desktopCamera'); return m; } - function getTargetPosition(targetObjectKey) { - if (targetObjectKey === 'floatingPoint2000' || targetObjectKey === 'floatingPoint5000' || targetObjectKey === 'floatingPoint10000' || targetObjectKey === 'floatingPoint20000') { - // figure out camera position and forward vector. choose a point distance D along the forward vector. - var floatingPointDistance = parseInt(targetObjectKey.split('floatingPoint')[1]); // pulls out 2000, 5000, or 10000 from targetObjectKey - var mCamera = destinationCameraMatrix; - var vCamZ = normalize([mCamera[2], mCamera[6], mCamera[10]]); // this is the forward vector - var relativePos = scalarMultiply(vCamZ, floatingPointDistance); - var targetPos = add([cameraX, cameraY, cameraZ], negate(relativePos)); // not sure why it has to be negated, but flips camera otherwise - return { - x: targetPos[0], - y: targetPos[1], - z: targetPos[2] - }; - } - - var targetPosition = realityEditor.sceneGraph.getWorldPosition(targetObjectKey); - return { - x: targetPosition.x, - y: targetPosition.y, - z: targetPosition.z - }; - } - function multiplyMatrixVector(M, v) { return [M[0] * v[0] + M[1] * v[1] + M[2] * v[2], M[3] * v[0] + M[4] * v[1] + M[5] * v[2], @@ -664,7 +582,6 @@ createNameSpace('realityEditor.device.desktopCamera'); console.log('add desktop camera keyboard, mouse, and 3d mouse controls'); - cameraTranslationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); destinationCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); // set up the keyStates map with default value of "up" for each key @@ -697,27 +614,6 @@ createNameSpace('realityEditor.device.desktopCamera'); } }); - var prevScrollTop = 0; - var direction = 'neutral'; - document.addEventListener('scroll', function(event) { - var newScrollTop = event.currentTarget.scrollingElement.scrollTop; - - if (direction !== 'up' && newScrollTop < 0) { - direction = 'up'; - } else if (direction !== 'down' && newScrollTop > 0) { - direction = 'down'; - } - - if (newScrollTop > prevScrollTop && direction === 'down') { - cameraVelocityZ -= cameraSpeed * keyboardSpeedMultiplier.scale; - } else if (newScrollTop < prevScrollTop && direction === 'up') { - cameraVelocityZ += cameraSpeed * keyboardSpeedMultiplier.scale; - } - - prevScrollTop = newScrollTop; - event.preventDefault(); - }); - window.addEventListener("wheel", function(event) { let scrollAmount = event.deltaY; unprocessedScrollDY += scrollAmount; @@ -807,27 +703,9 @@ createNameSpace('realityEditor.device.desktopCamera'); } function resetCamera() { - rotations = {}; - cameraX = 0; //-500; //-1500; //0; - cameraY = 0; //-11673; //7639; //0; - cameraZ = -7000; //13307; //-12993; //5000; - cameraVelocityX = 0; - cameraVelocityY = 0; - cameraVelocityZ = 0; - cameraPitch = 0; - cameraRoll = 0; - cameraYaw = 0; - cameraTranslationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); - cameraRotationMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); - // cameraUpVector = [0.058374143427579205, -0.9982947757947471, 0]; - // destinationCameraMatrix = realityEditor.gui.ar.utilities.newIdentityMatrix(); - - // realityEditor.gui.ar.draw.correctedCameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 600, 200, -4750, 1]; - // destinationCameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 600, 200, -4750, 1]; - - // realityEditor.gui.ar.draw.correctedCameraMatrix = [-0.9979047869606354,0.04357977057008431,-0.04782091339682644,0,0,0.7391224253723763,0.673571110063114,0,0.06469958393257225,0.6721598350903706,-0.7375738064290496,0,-7.3603317075507955e-62,1.8189894035458557e-12,-13467.956791262939,1]; - // destinationCameraMatrix = [-0.9979047869606354,0.04357977057008431,-0.04782091339682644,0,0,0.7391224253723763,0.673571110063114,0,0.06469958393257225,0.6721598350903706,-0.7375738064290496,0,-7.3603317075507955e-62,1.8189894035458557e-12,-13467.956791262939,1]; - + // all that's needed to fully reset the camera is to set its location and what it's looking at + cameraPosition = [1800, 7300, -5300]; + cameraTargetPosition = [0, 0, 0]; } // Working look-at matrix generator (with a set of vector3 math functions) @@ -880,29 +758,3 @@ createNameSpace('realityEditor.device.desktopCamera'); realityEditor.addons.addCallback('init', initService); })(); - - var gp_makeRotationX = function ( theta ) { - var c = Math.cos( theta ), s = Math.sin( theta ); - return [1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1]; - }; - - var gp_makeRotationY = function ( theta ) { - var c = Math.cos( theta ), s = Math.sin( theta ); - return [c, 0, s, 0, - 0, 1, 0, 0, - -s, 0, c, 0, - 0, 0, 0, 1]; - }; - - var gp_makeRotationZ = function ( theta ) { - var c = Math.cos( theta ), s = Math.sin( theta ); - return [c, -s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]; - }; - - window.gpMat = gp_makeRotationY(0); diff --git a/content_scripts/desktopRenderer.js b/content_scripts/desktopRenderer.js index 9c0e0c22..41114838 100644 --- a/content_scripts/desktopRenderer.js +++ b/content_scripts/desktopRenderer.js @@ -7,16 +7,6 @@ createNameSpace('realityEditor.gui.ar.desktopRenderer'); */ (function(exports) { - - var TEMP_DISABLE_MARKER_PLANES = true; - - var visibleObjectsCopy = {}; - var elementsAdded = []; - - var utilities = realityEditor.gui.ar.utilities; - var tempResMatrix = []; - var activeObjectMatrix = []; - /** * @type {Canvas} - the DOM element where the images streamed from a reality zone are rendered */ @@ -47,37 +37,6 @@ createNameSpace('realityEditor.gui.ar.desktopRenderer'); function initService() { if (!realityEditor.device.desktopAdapter.isDesktop()) { return; } - // if (!TEMP_DISABLE_MARKER_PLANES) { - // - // // registers a callback to the gui.ar.draw.update loop so that this module can manage its own rendering - // realityEditor.gui.ar.draw.addUpdateListener(function(visibleObjects) { - // - // // remove old plane elements that have disappeared - // for (var objectKey in visibleObjectsCopy) { - // if (!visibleObjectsCopy.hasOwnProperty(objectKey)) continue; - // if (!visibleObjects.hasOwnProperty(objectKey)) { - // removePlaneElement(objectKey); - // } - // } - // - // // cache the most recent visible objects so we can detect when one disappears - // visibleObjectsCopy = visibleObjects; - // - // for (objectKey in visibleObjects) { - // if (!visibleObjects.hasOwnProperty(objectKey)) continue; - // if (!objects.hasOwnProperty(objectKey)) continue; - // - // var object = realityEditor.getObject(objectKey); - // if (object.isWorldObject) continue; - // if (object.hasOwnProperty('targetType') && object.targetType === 'model') continue; - // - // renderMarkerPlane(objectKey, visibleObjects[objectKey]); - // } - // - // }); - // - // } - // create background canvas and supporting canvasses backgroundCanvas = document.createElement('canvas'); @@ -224,86 +183,6 @@ createNameSpace('realityEditor.gui.ar.desktopRenderer'); }); } - // function renderMarkerPlane(objectKey, visibleObjectMatrix) { - // // var object = realityEditor.getObject(objectKey); - // - // // create div for ghost if needed - // if (!globalDOMCache['plane' + objectKey]) { - // createPlaneElement(objectKey); - // } else { - // if (globalDOMCache['plane' + objectKey].style.display === 'none') { - // globalDOMCache['plane' + objectKey].style.display = 'inline'; - // } - // } - // - // utilities.multiplyMatrix(visibleObjectMatrix, globalStates.projectionMatrix, activeObjectMatrix); - // - // var finalMatrix = activeObjectMatrix; - // - // // adjust Z-index so it gets rendered behind all the real frames/nodes - // // calculate center Z of frame to know if it is mostly in front or behind the marker plane - // var projectedPoint = realityEditor.gui.ar.utilities.multiplyMatrix4([0, 0, 0, 1], activeObjectMatrix); - // finalMatrix[14] = -5 + 1000000 / Math.max(10, projectedPoint[2]); // (don't add extra 200) so it goes behind real - // - // if (globalStates.guiState !== 'ui') { - // finalMatrix[14] = 100; - // } - // - // // actually adjust the CSS to draw it with the correct transformation - // globalDOMCache['plane' + objectKey].style.transform = 'matrix3d(' + finalMatrix.toString() + ')'; // TODO: simplify to something meaningful - // - // // // store the screenX and screenY within the ghost to help us later draw lines to the ghosts - // // var ghostCenterPosition = getDomElementCenterPosition(globalDOMCache['ghost' + activeKey]); - // // ghostVehicle.screenX = ghostCenterPosition.x; - // // ghostVehicle.screenY = ghostCenterPosition.y; - // - // } - - // /** - // * Creates a dotted-outline DOM element for the given frame or node, using its width and height. - // * Styles it differently (red) if the reason for the ghost is that the frame/node was deleted. - // * Also add it to the elementsAdded list, to keep track of which ghosts are in existence. - // * @param {string} objectKey - // */ - // function createPlaneElement(objectKey) { - // var object = realityEditor.getObject(objectKey); - // - // var planeDiv = document.createElement('div'); - // planeDiv.id = 'plane' + objectKey; - // planeDiv.classList.add('main', 'ignorePointerEvents', 'visibleFrameContainer'); - // - // planeDiv.style.width = globalStates.height + 'px'; - // planeDiv.style.height = globalStates.width + 'px'; - // planeDiv.style.left = 0; - // planeDiv.style.top = 0; - // - // var innerPlane = document.createElement('img'); - // innerPlane.classList.add('markerPlaneElement'); - // var innerWidth = object.targetSize.width * 1000; - // var innerHeight = object.targetSize.height * 1000; - // innerPlane.style.width = innerWidth + 'px'; - // innerPlane.style.height = innerHeight + 'px'; - // innerPlane.style.left = (globalStates.height - innerWidth) / 2 + 'px'; - // innerPlane.style.top = (globalStates.width - innerHeight) / 2 + 'px'; - // - // var objectName = objectKey.slice(0, -12); // get objectName from objectId - // innerPlane.src = 'http://' + object.ip + ':' + httpPort + '/obj/' + objectName + '/target/target.jpg'; - // - // planeDiv.appendChild(innerPlane); - // - // document.getElementById('GUI').appendChild(planeDiv); - // globalDOMCache['plane' + objectKey] = planeDiv; - // - // // maintain an elementsAdded list so that we can remove them all on demand - // elementsAdded.push(objectKey); - // } - // - // function removePlaneElement(objectKey) { - // if (globalDOMCache['plane' + objectKey]) { - // globalDOMCache['plane' + objectKey].style.display = 'none'; - // } - // } - exports.processImageFromSource = processImageFromSource; realityEditor.addons.addCallback('init', initService); diff --git a/content_scripts/desktopStats.js b/content_scripts/desktopStats.js index feb92fcc..3b134be1 100644 --- a/content_scripts/desktopStats.js +++ b/content_scripts/desktopStats.js @@ -75,7 +75,7 @@ createNameSpace('realityEditor.device.desktopStats'); function updateImagesPerSecond() { currentImageTime = (new Date()).getTime() - imageStartTime; - imagesPerSecond = numImages / (currentImageTime/1000); + imagesPerSecond = numImages / (currentImageTime / 1000); imagesPerSecondElement.innerText = imagesPerSecond.toFixed(2); } From ad970d6579cf2c994f4abfc49f3d7d42b31a9d56 Mon Sep 17 00:00:00 2001 From: Ben Reynolds <32580292+benptc@users.noreply.github.com> Date: Thu, 10 Dec 2020 12:57:14 -0500 Subject: [PATCH 017/658] Update README.md --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 879ce2c4..71fb81c5 100644 --- a/README.md +++ b/README.md @@ -1 +1,28 @@ -# vuforia-spatial-remote-operator-addon \ No newline at end of file +# vuforia-spatial-remote-operator-addon + +The Remote Operator is an add-on for [Vuforia Spatial Toolbox](https://github.com/ptcrealitylab/vuforia-spatial-toolbox-ios) that makes it compatible with the [Azure Kinect Reality Zone](https://github.com/ptcrealitylab/AzureKinectRealityZone) Unity project. The result is a browser-based web app that combines a live volumetric capture of a space with mixed reality content. + +Full installation instructions are in the Azure Kinect Reality Zone [README](https://github.com/ptcrealitylab/AzureKinectRealityZone#azure-kinect-reality-zone). + +This add-on contains two hardware interfaces: +1. `pass`: This interface provides a websocket-based communication layer between the Vuforia Spatial Edge Server and the Unity application. +2. `desktopEditor`: This interface will serve the Remote Operator web app on `localhost:8081`. + +The add-on also contains some `content_scripts` that will modify the [Vuforia Spatial Toolbox User Interface](https://github.com/ptcrealitylab/vuforia-spatial-toolbox-userinterface) to be able to run in a desktop browser environment in addition to on a mobile AR device. + +**Special Setup instructions:** +1. Make sure to turn on both of these hardware interfaces in the "Manage Hardware Interfaces" tab of your Edge Server's web interface (`localhost:8080`) +2. The `desktopEditor` interface needs to be configured with a path to a local copy of the [Vuforia Spatial Toolbox User Interface](https://github.com/ptcrealitylab/vuforia-spatial-toolbox-userinterface) repository. Click on the yellow gear on the `desktopEditor` interface to view its configurations, and type in the path (e.g. `/Users/Benjamin/Documents/vuforia-spatial-toolbox-userinterface`) and hit save. +3. After configuring, restart your edge server to serve the web app on port 8081. + +**Using the Remote Operator:** +1. Objects, Tools, and Nodes will only appear in the Remote Operator if they have been localized against a World Object. To do this, make sure you have a World Object set up on your edge server and give it an Image Target or Area Target. With your Vuforia Spatial Toolbox iOS app, look at that target first and then look at the other Objects in your space. This will save their locations (relative to the World Object's origin) in the edge server. The World Object's position will be treated as the (0,0,0) position in your Unity project, and all localized Object, Tools, and Nodes will be rendered relative to that. +2. The Remote Operator background will be blank until you connect to an Azure Kinect Reality Zone. It will attempt to discover the possible Reality Zones in this network. Make sure your Unity project is running. Click on the drop-down menu in the top left to select a Reality Zone to connect to. Once connected, this Reality Zone must be designated as the "Primary Zone". Go into the Settings menu (click the gear icon) and type in the IP address of the computer running the Unity project into the "Primary Zone IP" field (e.g. `192.168.0.12`) and toggle this mode on. You should now see the video stream in the background. +3. To control the camera of the Remote Operator: + - `Right-click`: Rotate Camera + - `Shift + Right-click`: Pan Camera + - `Scroll Wheel`: Zoom + - `V`: Toggle visibility of extra UI +4. You can select multiple Reality Zones running on two different computers to receive a combined volumetric video for a larger area. Click on both Reality Zones in the drop-down menu to connect to them both, and ensure that one of them is set as the Primary Zone. + +Please use the [forum](https://forum.spatialtoolbox.vuforia.com) for any questions. From f132eb34f248408777cb5da8112667fbfbc9d66a Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 11 Dec 2020 11:59:59 -0500 Subject: [PATCH 018/658] adds gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b3467ea8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# idea editing tools +.idea + +# osx directory files +.DS_Store From 4e17bfc322ded2e3f54923dd193e5d22ac920d53 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 11 Dec 2020 12:00:25 -0500 Subject: [PATCH 019/658] adds MPL v2 license file --- LICENSE | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e87a115e --- /dev/null +++ b/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + From ecedf22eba61d11d1b3de6cfb7d5d9be4b799aa8 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 11 Dec 2020 12:07:46 -0500 Subject: [PATCH 020/658] applies gitignore changes --- .../inspectionProfiles/profiles_settings.xml | 5 -- .idea/misc.xml | 81 ------------------- .idea/modules.xml | 8 -- .idea/vcs.xml | 6 -- .../vuforia-spatial-remote-operator-addon.iml | 12 --- .idea/workspace.xml | 60 -------------- .../inspectionProfiles/profiles_settings.xml | 5 -- interfaces/.idea/interfaces.iml | 12 --- interfaces/.idea/misc.xml | 81 ------------------- interfaces/.idea/modules.xml | 8 -- interfaces/.idea/vcs.xml | 6 -- interfaces/.idea/workspace.xml | 61 -------------- 12 files changed, 345 deletions(-) delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/vuforia-spatial-remote-operator-addon.iml delete mode 100644 .idea/workspace.xml delete mode 100644 interfaces/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 interfaces/.idea/interfaces.iml delete mode 100644 interfaces/.idea/misc.xml delete mode 100644 interfaces/.idea/modules.xml delete mode 100644 interfaces/.idea/vcs.xml delete mode 100644 interfaces/.idea/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 0eefe328..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4dab1693..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 963cc104..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vuforia-spatial-remote-operator-addon.iml b/.idea/vuforia-spatial-remote-operator-addon.iml deleted file mode 100644 index 24643cc3..00000000 --- a/.idea/vuforia-spatial-remote-operator-addon.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index cea37a82..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - 1602597440248 - - - - - - - - - \ No newline at end of file diff --git a/interfaces/.idea/inspectionProfiles/profiles_settings.xml b/interfaces/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 0eefe328..00000000 --- a/interfaces/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/interfaces/.idea/interfaces.iml b/interfaces/.idea/interfaces.iml deleted file mode 100644 index 24643cc3..00000000 --- a/interfaces/.idea/interfaces.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/interfaces/.idea/misc.xml b/interfaces/.idea/misc.xml deleted file mode 100644 index 4dab1693..00000000 --- a/interfaces/.idea/misc.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/interfaces/.idea/modules.xml b/interfaces/.idea/modules.xml deleted file mode 100644 index 2d64fe0e..00000000 --- a/interfaces/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/interfaces/.idea/vcs.xml b/interfaces/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/interfaces/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/interfaces/.idea/workspace.xml b/interfaces/.idea/workspace.xml deleted file mode 100644 index 200614c3..00000000 --- a/interfaces/.idea/workspace.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - 1602597122892 - - - - - - - - - \ No newline at end of file From 57acc382470d685d037d9a92dfa49966ae7f977d Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 11 Dec 2020 14:49:29 -0500 Subject: [PATCH 021/658] adds license info --- content_scripts/desktopAdapter.js | 8 ++ content_scripts/desktopCamera.js | 8 ++ content_scripts/desktopRenderer.js | 8 ++ content_scripts/desktopStats.js | 8 ++ interfaces/desktopEditor/index.js | 10 ++- interfaces/desktopEditor/public/hololens.html | 85 ------------------- interfaces/pass/index.js | 8 ++ 7 files changed, 47 insertions(+), 88 deletions(-) delete mode 100644 interfaces/desktopEditor/public/hololens.html diff --git a/content_scripts/desktopAdapter.js b/content_scripts/desktopAdapter.js index c290c176..8da4a300 100644 --- a/content_scripts/desktopAdapter.js +++ b/content_scripts/desktopAdapter.js @@ -1,3 +1,11 @@ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + createNameSpace('realityEditor.device.desktopAdapter'); /** diff --git a/content_scripts/desktopCamera.js b/content_scripts/desktopCamera.js index fbf63967..ea4b0328 100644 --- a/content_scripts/desktopCamera.js +++ b/content_scripts/desktopCamera.js @@ -1,3 +1,11 @@ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + createNameSpace('realityEditor.device.desktopCamera'); /** diff --git a/content_scripts/desktopRenderer.js b/content_scripts/desktopRenderer.js index 41114838..63892a35 100644 --- a/content_scripts/desktopRenderer.js +++ b/content_scripts/desktopRenderer.js @@ -1,3 +1,11 @@ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + createNameSpace('realityEditor.gui.ar.desktopRenderer'); /** diff --git a/content_scripts/desktopStats.js b/content_scripts/desktopStats.js index 3b134be1..6c0a99dc 100644 --- a/content_scripts/desktopStats.js +++ b/content_scripts/desktopStats.js @@ -1,3 +1,11 @@ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + createNameSpace('realityEditor.device.desktopStats'); /** diff --git a/interfaces/desktopEditor/index.js b/interfaces/desktopEditor/index.js index 6467087d..065c7322 100644 --- a/interfaces/desktopEditor/index.js +++ b/interfaces/desktopEditor/index.js @@ -1,6 +1,10 @@ -/** - * Created by Ben Reynolds on 10/01/18. - */ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ /** * Set to true to enable the hardware interface diff --git a/interfaces/desktopEditor/public/hololens.html b/interfaces/desktopEditor/public/hololens.html deleted file mode 100644 index 4611b887..00000000 --- a/interfaces/desktopEditor/public/hololens.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - HoloLens Editor - - - - - - - - - - - - diff --git a/interfaces/pass/index.js b/interfaces/pass/index.js index 91b4597a..3092ea55 100644 --- a/interfaces/pass/index.js +++ b/interfaces/pass/index.js @@ -1,3 +1,11 @@ +/* +* Copyright © 2018 PTC +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + var server = require('@libraries/hardwareInterfaces'); var utilities = require('@libraries/utilities'); From c3e12ae5400e4ee0384ba29875fea25f47478dc1 Mon Sep 17 00:00:00 2001 From: Ben Reynolds Date: Fri, 11 Dec 2020 15:36:07 -0500 Subject: [PATCH 022/658] remove old ui application for controlling unity project --- interfaces/pass/public/ball.png | Bin 14499 -> 0 bytes interfaces/pass/public/clear.png | Bin 13857 -> 0 bytes interfaces/pass/public/index.html | 385 --- interfaces/pass/public/jquery-1.11.0.min.js | 4 - .../pass/public/jquery-migrate-1.2.1.min.js | 2 - interfaces/pass/public/record.png | Bin 17226 -> 0 bytes interfaces/pass/public/slick/ajax-loader.gif | Bin 4178 -> 0 bytes interfaces/pass/public/slick/config.rb | 10 - interfaces/pass/public/slick/fonts/slick.eot | Bin 2048 -> 0 bytes interfaces/pass/public/slick/fonts/slick.svg | 14 - interfaces/pass/public/slick/fonts/slick.woff | Bin 1380 -> 0 bytes interfaces/pass/public/slick/slick-theme.css | 204 -- interfaces/pass/public/slick/slick-theme.less | 168 - interfaces/pass/public/slick/slick-theme.scss | 194 -- interfaces/pass/public/slick/slick.css | 119 - interfaces/pass/public/slick/slick.js | 3011 ----------------- interfaces/pass/public/slick/slick.less | 100 - interfaces/pass/public/slick/slick.min.js | 1 - interfaces/pass/public/slick/slick.scss | 100 - interfaces/pass/public/stop.png | Bin 4459 -> 0 bytes interfaces/pass/public/twin.png | Bin 35685 -> 0 bytes 21 files changed, 4312 deletions(-) delete mode 100644 interfaces/pass/public/ball.png delete mode 100644 interfaces/pass/public/clear.png delete mode 100644 interfaces/pass/public/index.html delete mode 100644 interfaces/pass/public/jquery-1.11.0.min.js delete mode 100644 interfaces/pass/public/jquery-migrate-1.2.1.min.js delete mode 100644 interfaces/pass/public/record.png delete mode 100644 interfaces/pass/public/slick/ajax-loader.gif delete mode 100644 interfaces/pass/public/slick/config.rb delete mode 100644 interfaces/pass/public/slick/fonts/slick.eot delete mode 100644 interfaces/pass/public/slick/fonts/slick.svg delete mode 100644 interfaces/pass/public/slick/fonts/slick.woff delete mode 100644 interfaces/pass/public/slick/slick-theme.css delete mode 100644 interfaces/pass/public/slick/slick-theme.less delete mode 100644 interfaces/pass/public/slick/slick-theme.scss delete mode 100644 interfaces/pass/public/slick/slick.css delete mode 100644 interfaces/pass/public/slick/slick.js delete mode 100644 interfaces/pass/public/slick/slick.less delete mode 100644 interfaces/pass/public/slick/slick.min.js delete mode 100644 interfaces/pass/public/slick/slick.scss delete mode 100644 interfaces/pass/public/stop.png delete mode 100644 interfaces/pass/public/twin.png diff --git a/interfaces/pass/public/ball.png b/interfaces/pass/public/ball.png deleted file mode 100644 index eaa9bd4a532149e0e95843270861ac074beb0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14499 zcmZ{LWn5I<7w!N8k}9EecXyXccSsE&9Yc2uBM3@&gEUe@jtEG1$4Ga#bmu+3_y6U7 zxF7fpXYIY#e%5-{-e;d3Vd|=KIG7}uAP@*gL0(!D1VRS>MFu^82K;<{+D zc?$wn$6(!?q5{uU=JJ{zqKeY+6a*4$mx4QN_hJ$^?Bcl1u@Xp>M;qA{}%x@J)*>~T*@EuPZanI$=+Lac)%q55INE2sy23Cl(dp+r*8hAeG3KQB}iHgp2 z2@K3i32)k)pxEhmf_L+nE12VSq%ag-+>Xbf?8r%87BY(Oyqc|L(HpeYsY#Ey{uZO( zqgkgwDuod&0$R3df5bMSLLqLGLL1Z3%l2TidKKZ5EyIT34st3+ISy{YHf4HGG@t=b z&?zDw&>s@p;)!YSmx_XmC~{9A(WMBGjc|o>N78mJpS{)Vwcq0UD%_;@6V9U;0F*|( z#@^D4-X=oRVt_@`)*Ulc@&xO%CFsaNzKK8!#^T~K+g!+0&>`A17Q(fu?SoCKA#fJV zVwBh)D7`%Cab&gD8XQ>m=D3_EY@F5a!AbV{xKiPDksExf>wU`8?YJMUNd9k{|`7m=>Xr%80bd+%3`}>`b&8RRcJ@N#SvSFupgQdBvdAA%a1up)0hc$eYqSvlM7ZXFOcR^LhBic%r9Q>HfHXW_ z0?hsWW^LG@Tlu3|WVFMuIxpc56yr7DTZOV*y%T}fH42u(NqLbyQ2GznY1fPIWqbs! zdPD5WlYcvhb8&OH7VFc)+>>?QV|2^ir;GBP=uuA~ZTukBb&G1b(Fzjz98`>AqbOIg zKKEgJYnVnUZD_I4Ll5#@N7*FFo4|mcByCja(u4m)lMe z*)!JYSeFSEeEnR9DTv>R*8Tn84d>{6uZ$Zgp5RPh)eUiUl>$gjSY4!fy0=$fvi}glrIThSt;YElZ2kVq z8M($AmQ|tbpVS%cqLEm~3sWMmof(Rb9*QEjE@DT&k2>!%ls_vZ_X;h4^f4r`{n14g z&`xrKyHPpQdPS<$A;b2(uv4{S+5?I5=0jiVlL*83z-I~=QRk-*;a&{Obk}~Z-0W#> zd+Lp1KZJ81^1g%}U~8=Yt{-;c84FN0EAjqu`rdZ>YSB?+zk|T@%UDUKlDU1WBGaY0 zZ!y|65em(C8ESq*4CV=LvW0#R!DU3T(ZT41x<*otrJ_Fdf|}*#rwZ?#I-G-QoGDfMi^N&hOQ$1M2WsrMl0%F(j-6`!Kr@b zg_Iux=dk3v3c=L7Wz`tDir@|RLbiL1JH%6d&7Mj&lka570}Hu0>iD9b|7KZwt9dz< zhXGBZzahBtxn1Q!uHUz;Wi>Y_{Ps8Vs>LD3&kvhl*|vUT4CGZws-bf9Q)V9EYrNx$ ze1WWEB8-oh@~VUv-&?6EL6)}P`$*7|Ec+ST_&Cn#*@l#BYu#Wa$&|1fz$Z zeM%*)QjmB@3E7MiPL{0JZ^B1UlJQUZh+yC=vR-j%ljQ5RU%=k%l+ykg6^1A^`12W~ ziEb&)VL2k8>57zBVx_T7Ifl##Q;2`%V}-7;H29(o+ccaJA{#Hh%qbiA5&Od)eYye{ zM+J7`?62_oCgVCt!tr^2OUo6hW>0|>yM43v5)=JPdS%=RzLcV3S6oZQ9WlA_rmH`0 zwvR+hFHC~73>jeFjgaAGWd0F9Ew2+T1CDLf6IJ8sgCleIJ(^YWYd?^|hcgsN_aCJc zsqK5#R63Zq1z88dYNGiOFLU>5<%vCxNkXfQ;t7*b>iEcHGmo*K5w<@<&So7Q9w(W@Z|BFevxVx>nfH$#di>LqrY^xnq9}&P zAt1XNr*UJjgN7n`3~J5Il9T*0$Y500EfGG~!pkk?y`Sxyiw}CZHf_WCb75&3x_pNN zAvJmB+q{K~*?BRAR%xZ!WA!+!CHne#O{L26-A$^Ac*R^t2}1F1fNr1RcUm<Dg(EPEzG&gRfm5szd^!^Bq-KNLr-`0- zTMV*KEj|(Y%Cly9qY>%zr2EAoq`mgbdRec7;0OuRyoTUSI@gL#in1g!YFLdI@a_(6XREdSZAU}{b43Wvx^i56rl`Y)ia?8n z+FaenddcWuuJJ;1t`z(!EeICBurw6)X_7AzO%9WxZ|nwQMR03|F3&_3fyN0uKpKc7 zUD({oynCXKFoyfjp$ozHXE0P*aw)hwe1Ya3Ui8ADzgCCAFPH-`71;yIE28BspF4X} zU}aOn(1H!|i}ET+hyP(HW-3UnOkfoAGXI- zz@|szL{efi4bZGi8d%k{d>Eeg?))<99%wBO%SjJ@=z$%tBjgyWXLQ`m)jh4&9VP<3 z-Wf5Bc-=y0z}^(2T{mY*Mg? zt%@TlI)av|!qDKtNo8VdFqyk?(jo1;MSa_o$ERd?W667RC^kMD_<c&elID{N=hgINu->XUVfDWOy%$%9mj85>0pionbt0&N2I_cQ!ojb z{^dt?Ypnj=HMk?`l3ia5RF;HzXoZV_6dKQ3TqyO@D+&b3+2RB2Cs-wD-v>i$$ZV^T z9!vO)+yYZvs>_7rz_SFvL^1tOG2bE9a)Qnj;T!N5NuDeUYS#&*KJ$PpZPZ>x*8QM> z*BR856Mnn2GUQTt*R?KzdeZ0+%TCP!x1R1#TINr$fC(4|yt zMjRj|KDTUeX|2}-&vIc~|1jld6@Cw%#Rlea%>Hxr*5%fc5X)dh^i|};0uExp3fTkH zzdGOZiFSKGXeFyhrpY)su-aq`gd3;BOM5?DA85!3vwb-t$gxDzywAws{w$Vmj(fD~ z>w!rsyr%Aa6Mwy9*J0NsmlkT_B4mK$*Dp4w$ZFTy*>m*dq*#_t4Jh!n*e|FBP{T@b z-WUu$CS9we$BI4L&S^Qu)AnTO?Q5jSukz7We8CULi`Yu%nRZ z;yP!nX3SeyVWk+1q>PXC8sN#HTwJO&Lx&l2Pr9e*$WM9}dZ};tc6ASmQREru#Y|`H zTosnN&#*d!68AlfkpU^o*5HDD^cdUvEYAbt&KTZNdu03UK_#r_Ts zeR60rXh?D7*GOc*&WTxNymL(```YLV`r%U1{X?!%(^x|@Sfw&`$5swXiar+p6jojw z9*<-18{!ZXk*W~tmLj4LbPiddi^X%diZPnzwzb&TeP~Yqh)}e~<3;C^2M;SFI@b<| zz&C9&(V#b4&}_ugDJgyC%Mctw#tC`WSOwLprH%P)yADZ#CKRFg6Qq_x4UkNUS}eth zxbN3b_TK2n5mH}$Cy6KC)^Q%ntf}y>0!!$rGKoF0zc|a34=q+1tWHtNnZ=pwj;`}nv#}?eueSskdw7T` zY8ac}+*^|PV>+XVzkhAOh!l>z>eoRfL3v&VRPaLo9SeM_C4BjjrA)ng)vb!_Yb~XY z?wl2z_-qdpALfV}opCI_Dpm1KYt_*nQ`oMAPz4*0lLSSc#_Ff0vIKTr0@cj(713x- zyj>zGjfL|dlzOqg=^!C!g+Hr?j4na8%OsW~CJ);4HBd#Nc}I8!)8ks{8?rV_m*RQu zjiQiSO>$pmjp}y$y0q)NZA}@keQBzY%};j;4VSdgO3^07Z|Po;Vh{1I^Q;Heo4J7Q zDJ1yTylO;c+UG?3^-IF*PX=CQ`=E-PO05t%;Rk-16P#bTEApxlnzOIj+y#vP!^;TZ zC3%bgnIL)lfS`&LDbl0GLk>TfNuR-nCblE9V_bmpmm6p~tnC>I+J_n?0@xEctHBnv;D0D+_UR{+Iem?v=(y9H5wKVK)-Zt_eUquEHE6jDkCGB}ZZ04ry zByk)oxDyp|5oe~DDyn8x36=g z(E%gDJ97jfqeyu2^YX;BW#Kcd+Nadoj91|nL-cHv@rd_BqaO2G$;|C!IM;8aif<~z zBm#D3FXie}Z?-F`qtNPeWu*BNjo&F(te)^tOJ>e6I zfc?JN_LghS@_^D%?do)T2Wfr)5(&=p-qTdSDal}_S=NpQ3+nMMs`hW5TiI%SBrnNo zC(0ekJ=+zeRu!A1a)LbYUOr2)do0>ng06He56rvy<_5AX*wdl;q!V?0e>)Uux|JPM z>#cHy_Dm`-$>3_sk-HK!G2n+>bdn)t{dj|(LV{D({lEZQ({#+3c#5DIR=^LQQ6Eq< z7cMm_9|OMdt?hj z(1zUr5Hhl+9fd|jGuhEQV|eXaH}l&fIo9X?o$r4xn8=c0T%HbcxzPu578Nj{G0WZq z`1AZw!tr%!V*Onc8qz7B3(%@yBghoeZ?+Q{`AEjtPUw*#EIxA6H)5Bs&foCGIij&B zi~WA+bQa*fD3Icn@2QuNsVoW{+6Ijfe9-KKh7flSQrIU`bd{0dFn!zDhry2n+Cr7_ z$)#=0V*Tm7{zDR*?T!DLFR6q6Lvz8~Cn zQOw@v151um>8;Gx`%6%UNW6dmPF$z^A4Rw;e5UmLm}9ZrOAISQsc9rQHcI5VR zXJ$lW6u-1iF@dnxl5!dr@Op@bNkM&bAkZa*%*<(FQ7v)mrF@5>0!bjio=_^W?+7n7 z?6x5EsTO_{_?l*(7K+DkB0ms1Ozur}HV(Cb?^zk2ke3i-7f!_f?V^&XxXDmVzCx1+ z0?HO8Fvox1PusabyA>P%#e49YxQp$@^u0>|=@K+fVEcr&;8Ul0JC5jmQrPnkATk9~ zohl7*RBA&k<1Q;qy1;#%XRB5U5D3R&EJ%4l_&v`kcrhRToUNfGhVrEJr|ILJ2M#ZP zsDTTb1TvgzzC(NRa5bj3RGMX~Fw|L;N(Vfkdl-k(c|L$|iem@lzLia77R=&co3+$` zT2Brsi9)0MwLjh8azogvXjr`#FDn?^g&pY9S2Xb#M?PS>G6|WH?~$$|wPeVW=d1j5 z9excj(0_Kr(5?{|cAMKbSSNM$W}c@cg04C4!(VCGfrRPHcTcPcRVVAiX<~4c5xp7x zcyU#sQfG?KUI-(G`Yeo!vnA@#f`Yxjya!g4Ku5gE4G6_&CV@RX(Q1qqpO!;a+;baG zC}Un-DVcV)D!kPJeCYaR12sl@^yjI9eDFT$#RcR#X_Xs!bABa6rd{ zhS&O~@=XM;&Oi|blQW$m3xz4!{GALO+Zqu1OJ}M#+|l`kli#3|=Z@E3-*AqS4&9C{^L^EVun@S7xcA>CX zaDNa9{nkn$(W;wfjH!EMDZ@I(`OniUJU)`xrV8U;{f=MG_s*3pHNzLyJxRM?0n34W zHL=vpySE+-Otzz~huRxPZ}}9hNkZQMn@ILKW;G1h9F7{&$UwAjDhT+wPGjlT@ zkEeFlr}=Q(-Qh~wd-4GQG?WM>Z9Sc&0xPU_hCHgE*{&9CIt%G+Nde=>CfUxX;9wc>fs)_!40Gr zW!nfi+=dZ-cjphL_2P+|trTEfahJf~H`jGjUL{qeHDOvOvTg{I?iZmvWx6`kj8r@5 zVMFTz_b>|mc$WHRU(^--2N&&>*;JmR6gTZ75b>8EbG0Hz{j&0vpdJ@5&}5Zy)^7|V zW$YsxkqWt7jas8?y(3>&i#rhCrHsXAaW6Su8sAgZSrf`GLJ==-00U7AB=fbNh`KMI zZiMR4B|1W0p+DKH3+B+3>`%Ke>ULk2bI@a(dn&*1OVF?ngV_`nllHOn?*o~%kv7wD z`Zd{EM6LIb41<0Hh(}cVEr#wkp+sRib5kR|62Uqxks+ zop^_n+>4J20^zkxuBwx}sks!aTc~D*}`0 zcfSJbOYYW{K-$~QPkF8&Cs&`K=m+V}zyd;b&xFVHYb$Bh3L}V-qw|mQt)^!bcP_(N zC0;Hqu>t7n?J{x7?w8DkQD>Bk?KdU*w|3DNP3*8BWH!?hI3{;hbhW+bW0`{zv)g?F zzhU=~w>^!jDi|r`V4_3R@LkgSGMn2G$nllR4I$?)rSGkjr|s&p|L6&49g}I!H5o-4H(Jq1<++R0A0x4C&QzHQ!X~Kbd7PxW>Ww9>FU73moBtThRipbF0 z^r)G@_kd>>-!tLJ-0Pm=IU%CxRl(J1-yQ_%fSF!Fd^3q&Z-c%2xq)ScRTaNsQlq&f z!_G$#q-GmrLXqwElR|xO=W7kYU1iK6YAijeA1M7aG5-R}B8W@%aQBFnkf1~LXQP1d zW#{k>RPhZi71lptH?@sxU(JP4=8Iw$Q+)Dw57G->4-7bqSX2simmofd`%fL4g&#t1 zKil^_?&oWOglkBUifQkCrL=|6V<-OyIhjojWSiD&nOn4+RCw_GL)0U?Wz+4~MpF%=;s#drJW!2IY zlFfmnqzG9Q=}&J7`)QPczhuy!N32{8XuSq{?2GveXp{i1l=)K#V6bV20{X7pJhI!j z4b2Tbr}}9N$irA>fH%6-o7ekDn}zmV_r*Pr8H|iDsiH9qDYw*0bo(#)?#r$C=hD#c z$ntnr+bFof5@?he@BMa9z1zr69ODSX*3yUI~hIB$Z)l2)J<^bSNWvf}vg5mYzqspxemyF~<*S?VS+};~_l2 zj6ZSev6ZG6h3oAVa#!#n5?6jbik{X!NJr$F8P(l}UAZ zM2s#BK^4pOu6&RuhZ^uV5EvdJr6kluh8A*Sz98q6Tx<5g8q6I*`f&DfoD(~VBSFxH zfQn@TDOI!S*GN9vOl@x*dZssk}`)jCB+GwE&P4)p5=e6s~{Cv^ORO+9ESL$9I~F(zvp^P18D8qUGa^AUg5ezPwe30Y#bEt4H zkw3%e^A&QI@BkHZsPI1$Wq?60hDhTa|Mz9Z?OzX&XqxeP1Vf8V2|dt$=@Q?_&1~6(1FtYNBK^zW6N$u^)GbX1ul871eDPx_8-y%55 z-DF$1liU-9+J5A`-Na`~;K&sO20Yz=ng0zR$Z)F%PyM-_?C|mLw1rALI3%df|5y>8 zF~L_4E(v&#ejMLU%ZF(K4BnqVtbfqEcJi`| zO=KUQX2+21mwwCDQ!)NgY)o9r(Ez>q*IX!`D#vZw^ep%(P(Wh&>fJb{O>Z?~^Bmyp ziySRLjHxrJR6d2{5om>P*VR`Vh~yD4GW_$c_8SVv zy=}NWYF44r9*$87i>+GH)&^*o`;A;)P9&A&@fy!%(aw{fSFj}bX^nEn@Db0AW7 zf0|oVL{D*pAE4s={NK16-%w%yQ#Gx6+cUE3hyQsZ?nuRv2ZQO*;U8x&$^LOx(QbWu ze&CP0vK&qEVv>BWy>5;lbU=t~@p7URrzN3Y;y{^sK zr94AF`_ARPVO=kcPonb!b+U^|H?xDakjj|tG0D#9)KQe z?lzuHY8mS*^)Kz9DgT)rL7P7lJTYsL-1Y*A`qb^bNOsM2Z)gTSYTv4trMXJLA_BIU zJvZ;%a&g3CNW?D9c3u5|E)Wz8U?%M$_TQ?$59DAzJ{c+JNWabxee|a@@>EOh)IhL! z_n-rtCP9p;=aWX~R_|K^nfQ>I1c{(Y&Y#_9o2dZ=UjHG$!gh668C-sVSgAb)qs6qy zHy$kgvn~p-65eljo%`-dUGtc=S?{B}Lv<(;T1F_r&Hz|k%2FoK=ow!g>0m@Ch;_G< z@#d+-Ip1)2P=@kj$9Cc15k}*iHrz!NlYwm<9Z=Dmy+^(~oyO&r6J0*uQ1w0Fo!!Rj zj%+eOh?eb>b1ccf4SIw)E48(w`Na~BF*wKpVO1j<_?DMFJ839ZDU4nG$?YQ?x^6qM#^JZZEMJg}k8%puweLIKyJ!g0>5g6vaA8QAXrZ#nDtk%jl z%zsrh=)gLU1~p@~(cfu6Oa^JTpG7#R`h*jrmF^I=eGx#rNhU1j>2s=VdlpPbMR{U) zM#;1VDy^TFN;&~s%0Su|ft`^KEbuWGc|nrAn5BRJidG;_>!thiNi>uwPQ`iJlY3=< zlfMb6<&u^e5!mH=!=N`YzOySDwFQ6UoK^YLD@zWNIF)quJJPI7I(Ho@E-dpg|CGJM ztmU-p5>0ffiJ~niV{gY6AW4bwI;*<0=kuPC?2u$COgstl_Bz#A`jPKGNuMl=eekQ@ z$l~w{EvC#Q5K=~$XwdaLzVcLUw^WJA2!{i?a#iqmwoEX(;{>$<{%y5R6aEd`Z=sKknD}z6Iz)OSTp*kD!Y$9>QTK_2&rwY4 zl&PBjyq7;%$5Z8Y7Yq&tTIVaCdMk0ODx7N3RsA6moX}*bEy=_)y*mxnx z>Q>h?Sqmd7GLrmImjzab+%9JUL9qX=S`!1zIg)HxH&W7%V*1n$5`y2ECX2DIL&bTQ zxHHOD9=myrlm>b>X+4@Cq5eK_VD#2%XB0(6Mi5ef<9K};c7>-hZEx*hhoaiv3?6o` zik!@ackQpa5`+%kwtfC5e2-IFh)4HzMM#qEbql<44V%|icQhnWotlHIw0OJlFWu0i zzu9`N~@n|8l-0paT{YFbelAq>t!%Ch9vhDm)tWl6W_E8R+s4i_6Ke2j zTpY;{B?F~zqk9L->2~aFI-SFaJxmw4#vs_L5s67?npFFbUD`tP&+N$q$w8;u+r(q& zWbrOGBt*u0s!s$zU$+2dKk9?9?wncm9|V(^W|oD^-`1B|`WB`Q{0FQs=YvR|njw+7 z-rS*K*@334hMmjG@+EVTyUNj5_O(?@*kDCgGTtZd8l{urTh*O+4RInViU#aDBY$QC znOYo8n@Jp#bOd#Jh-NLsv(_|s7-ca(Ns&SZyF*7FJI(7Rn{-Nf-KmYRmiTM>U#x4D z@RCM;XnZ^8$(>sp_y%tMJ%n)DEeGDgZ7LwZ^204tG0n1JIW~@#?8#x=+2hhUwKLLn zak9T`oYRLMdRfj_BRiBZ;3%6GWOogNKbWofXFZ6nN4!ohUlo9On77$CHcKpBNw&pA z63ll~BVI=m{myz4x#R3HE;nqGLF~y6wb*mlbY89L!h%;V-^9qzb7_LP2lE=NK5e&R z+#N@U$P5xQ!JG>gEN3dD9kZHxs^Sr1+YeDP=CBO{h)45^x`2V6hiCY7dNjW>^AkJq z1-QCPzBvS*TxTI2IX)XmXK2QcUQ=up2n zfGX{etojY3pYs#e5}&4v9cCp?t>{&y-IFJL3kPf8(exXu!pKcpG@V&Y0^jl-Ww2P@W3&m&Py)|p`5-aC+VJ+Qy`f_P-P z3K7Z{sC!2-^(!gxk$s-+EVW!rK@AzA5Z4yQ9UVL3-9thFUf&gU6c_PfK$V_yyWH zVMg}g=|>~yvZTR>P+yH{;rDLx$XFerXNx#5 zo^;$Y;iXXx)AivxTv4K_eKtoO=0$e3{5uEB^vyWGWnljDx{wgqPRH{e-jKUgr9xBU zABwDti74gq!V~@Ak1MJb>uk8n{&*UH+Vx?|z$UnMrlU7okq(9G)NwsVAz7EcVQ*HG ziAR`!mc8nh&}|Q}lgKHmKV+_KDn@G&EhsQ3mGuyDYF<|74udzU*mS>*G{w{MSv-ym zG;Qd~0&|F467!OewCeDr&<~k&xasez51PQY2t!E&efc}_a@-o3Hkl(7v{4z)YTyIz zt5l&pUxJASsH`i$ObdNy(Pe2QXCd#mi?gs_u9_!gGo?6cU~NJT+_3A(hHWVqVeF)O z`tp2j>B;`s<1F!NtmfztSMp14Tvt7daHufyCQp~UZjLcN>hIasiU+xFg;3EAyUqU8 zJVhAMfRJ49-CBI?Vql2P@O__(y78Kx29ZrF=%!5pF@#{X{=qf+fqONkPuKIirBuipSoz>LwExxWC8`{V25G2NekE=0c~qdF2b zhNmdQWsQ0#vYCdg4e|pgX9G2vSKmflY7ftfE$%oekS@I0M(U9{snrmMN;LhQZH8+5 z_xW$tLT{RSeSyTOxxU=3=;T%8bzjAa7<@a;mAQFt#T866luNc}6hFsL)@}=-KM+#$ z?<0`a;r(;MrR6gC16yl+azL^3t~`NxGLzQfkZNYr^Ge_SlJhfUjqPdE3sHpIBf;&2 z86Q9Sn)Cx<%Dqa{;d-pk$1bK*p3JPZtqAb>LMz!{-sAAub`69TZYP}!iY$9Vp-LtM z_eEkz2jfjNJgie=z-^5oQWG#cti9$yrRc+;VGxI0U8!wm-70PUq_Dzq;`uV^-}#62 z#ua3j_p-DicWP*VQ(2c@=f%m_J<1XS0<@yz_zc?-tA^~=~ zSHeq)_=P@w>WO@27HSeZS)INyhM&L0}3Mq@S?G>vV`q;$M|<6dLM+@TAqs3jQjjWE2h);}nW9JAzYOE8%aG#22WS zNEZ5eccVBwEUW8%j0SuVXxbnl#I+)9WAN*D^88;FrF;&HhN_H^V3s!CHTL!-`>R`9 zEm{tI!5XC-N{AavB%u@P;YI|aCo)(vJYzQNgG&%xm{*ncb@(r2-T1g^1v{&68FN`p zc5%5nE-^+yV|0Uqvk(PwZ{vl9!;PK_MBMV>DGpyhO_hvTX0b-xslXN`O1dmMmSXF6(L;2sed^U-O| zaM@9uRm!PIzATnFAU^1@HN3X)cc3QG_xQhLlbLi51Io_cn5s_>$9EksrpbD(PFP&1 zv217^2C!G2PM39L6-c=unH`=dwl36?NdgvoCxJIkq>M_W++mq<;mw~3u@eL=O8*H{ zvQs|wwxgEJ%-z`fYKhQ;meroLPlX})3~47H!gMvf)0iB3CJ@0mYd=Y4OK-yA7zrbS z-K@+__M1`=-lD|$?tKBntEW&n(x(-YlW`^W10=+}Wht;c7{#CyFm#vbRU!2H&wujq+@>fhL7_dHjvK3kbHf{ zk6G=TXpl3X5uY`FB58qR2p|tzEyt@wM`6xCq&-4Yl!|I<0usw+@=tABz|4iiSFFQBI zFThE*@z;ASv%)Myglg`&Ma%u}5v-|%H{dBLy-0o%;L({=R@B1XayTh6WqF}U%3AF& z*W?)9?;o{Bmvnir(BzXE-tzu*T>NCcUX*{_u7-G!aV@G#T4yJBT#U}|#wmE|U>~K~ zTeGPowU4q8CrK#2`TnY0*3m!rOBh7~e&rScZCZzV_LKhi0c;KhU1RZAU3Rps%9B;ut(onZCukFyADk26VR-L&Ql6%` zWaqdPg?!IAPpl>~hsGM{$FeTg_{Tp`j44O&nvmIbk!qPXfak<8R<725j_K*_!2%$8 zj9T3pAE;+LJ|nhXF+c_bCzU6IC3e@}>4Xcy?oaFCkS_{TyRBD38go>NeC}%kEZwAf z5)IOGd_i=Oo5~>OM!pQk(6e-JUON`MT@D8#>Uj9QpaG}bC^CBk_vf&#M>dDo)D^FO z`NQm2Nx<@?@53TK;lCj$-{OPhKOf_NCHJeeL9Oy^h$4HH{G>?NSfXzwx|WXZsksLO zVWn=tWKOfWhT~kR948<@g#Ft1(O~4+;c$Q3ypL{5bKTl0+RhE z736(ahcxvahiTKuZPGiqMbI_U+BUDV%eSG^WiZ38gk&Mn-&DHH49_Mw2Fj9Q=I*&(Ye_i^@}UM^X#pjYQ%f73*7(0`p2xsN}*BbR#Fc!twLe5bj{ zvb0IQbt-T+OXCnEc5eDSn0UYhH%7d0%hqu_raW2Tb6_#bb46B;4LdWx0o|(DE3d(? z_JrfPUnH`v0_W{maLajtAo^JLRL3UAcI865zbShYaA4N^a@DM*ulUPsP3F(?`E^_gQ(nTv z+6b6^E;5wd9zDnX%2i)!PD_z-uJ*;>-7X3DLej+@8BSkK2BRFbiDTrzEu=aH)-}<7 zTew6mW%hQNZ;u#S@|oac7Y&3T@mU0vlITj3;`UMT*KuEMnp}Oxc5$)}oj7pfSS@4G zF$cIz!<-ksyDaDN+s-!Zo9R^%1vICB_<`HIZ@u^zt6?J~m9@-)<;(ot; z!vn~R#Cy5p@rmeLss&U_y%SP=!nXMuIV1jEk@~LYhTQ97UwSr^$QzN?)PYZVu0xa*po2?yFRBSYJVBp8_=?ZYM|F9wzPt? zH1p{|UV&^6bf3cs+~|54>iP^=O@f4Gu~~7Pr4EX1?SFsR0M^6>6?b~2(72qvXmBB3 zRbljOX0IajyJ(X4UoLY_AYH4zxpe{EjmJFfC~^9Z(bf!dmgx_Ib!vkw+1AaIYa(4C zC`_in`BMq-STMzT4r|s`pM-l3jsrxFRphVdONYw>fyEJAQ0Z(RM_o-+mc13}W}?;z z+Q4iZc&bJg{Q|Q7go1OcP@bn$*$v#sqj#6lbGI;cw-hmVwFG`ZJY3wu>|Ffpyxbpn zct!a5MR>Vbxp+jlxPTJp3{uy3Hm?4P~vO= diff --git a/interfaces/pass/public/clear.png b/interfaces/pass/public/clear.png deleted file mode 100644 index 41d110135d588c12d5280ae7a6afacbd6e019def..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13857 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 - - - - - - - - - - -
-
- - - - - - -
- - - - - -
- - -
-
Icons made by Pixel perfect from www.flaticon.com is licensed by CC 3.0 BY
- -
Icons made by Pixel perfect from www.flaticon.com is licensed by CC 3.0 BY
-
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
-
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
-
Icons made by mavadee from www.flaticon.com is licensed by CC 3.0 BY
-
- - - - - - - - - - - - - \ No newline at end of file diff --git a/interfaces/pass/public/jquery-1.11.0.min.js b/interfaces/pass/public/jquery-1.11.0.min.js deleted file mode 100644 index 73f33fb3..00000000 --- a/interfaces/pass/public/jquery-1.11.0.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f -}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("