├── .gitignore ├── emulator ├── images │ └── black_linen_v2.png ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── css │ ├── timeline.css │ └── emulator.css ├── js │ ├── supports.js │ ├── utils.js │ ├── emulator.js │ ├── timeliner.js │ └── libs │ │ └── jquery-1.10.2.min.js ├── screen.html └── index.html ├── .editorconfig ├── controller ├── css │ └── controls.css ├── index.html └── js │ ├── utils.js │ ├── controls.js │ ├── third_party │ ├── tween.min.js │ └── fulltilt.min.js │ ├── DeviceOrientationEmulatorControls.js │ └── app.js ├── README.md └── doe.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /tmp/* 3 | .grunt 4 | logs 5 | *.log 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /emulator/images/black_linen_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richtr/doe/HEAD/emulator/images/black_linen_v2.png -------------------------------------------------------------------------------- /emulator/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richtr/doe/HEAD/emulator/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /emulator/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richtr/doe/HEAD/emulator/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /emulator/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richtr/doe/HEAD/emulator/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /controller/css/controls.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | overflow: hidden; 4 | background-color: #ffffff; 5 | } 6 | #info { 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | right: 0; 11 | display: block; 12 | overflow: hidden; 13 | font-family: Arial, Helvetica, sans-serif; 14 | margin: 0; 15 | color: #ddd; 16 | background-color: #383636; 17 | padding: 5px; 18 | font-family: Monospace; 19 | font-size: 13px; 20 | font-weight: bold; 21 | text-align: center; 22 | z-index: 10; 23 | } 24 | .small { 25 | font-size: 0.8em; 26 | text-align: center; 27 | } 28 | .data_output { 29 | display: inline-block; 30 | position: relative; 31 | width: 40px; 32 | overflow: hidden; 33 | } 34 | -------------------------------------------------------------------------------- /controller/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
40 |
41 | ##### 1. The Web App Viewport
42 |
43 | This is where the web application that is being emulated is displayed.
44 |
45 | You can customize the scale of the viewport and the dimensions of the viewport via tools available in the emulator menu bar.
46 |
47 | ##### 2. The Device Controller
48 |
49 | The device controller is an interactive model of a real device. As a user of the emulator you can change the physical orientation of the 'device' within space.
50 |
51 | To change the physical orientation of the device just click on the device shown, and drag your mouse in the desired direction of movement. The device should move on screen.
52 |
53 | Any changes you make using your mouse in the device controller will produce updated device orientation data within the emulator menu bar.
54 |
55 | ##### 3. The Emulator Menu Bar
56 |
57 | The doe emulator menu bar lets you refine different important characteristics of your device within real world space. The values displayed or overridden in the menu bar will affect how the Web App Viewport will be oriented on the screen.
58 |
59 |
60 |
61 | ###### 3.1. Viewport Scaling
62 |
63 | Using the slider you can change the scale of the Web App Viewport. This does not affect the screen dimensions within the Web App Viewport.
64 |
65 | ###### 3.2. Device type
66 |
67 | Using this part of the menu bar lets you change the screen dimensions within the Web App Viewport to the dimensions that can be found on typical hardware devices.
68 |
69 | The device types currently available in the doe emulator, from left to right, are as follows:
70 |
71 | * iPhone
72 | * Android phone
73 | * Android tablet
74 | * iPad
75 |
76 | Just select one of these options and the Web App Viewport will update to the device's screen dimensions.
77 |
78 | ###### 3.3. Device Orientation Data
79 |
80 | This part of the emulator menu bar displays the current device orientation data that is being emitted from the Device Controller.
81 |
82 | The data is as follows:
83 |
84 | * 'a' (or `DeviceOrientationEvent.alpha`) in the range (0, 360)
85 | * 'b' (or `DeviceOrientationEvent.beta`) in the range (-180, 180)
86 | * 'g' (or `DeviceOrientationEvent.gamma`) in the range (-90, 90)
87 |
88 | You can also manually enter device orientation data here. Just select the value shown next to 'a', 'b' or 'g' and enter a new value. The Device Controller will automatically readjust to orient to the chosen values.
89 |
90 | The button on the right side allows you to reset the device orientation data to its default position.
91 |
92 | ###### 3.4. Screen Orientation Data
93 |
94 | This part of the emulator menu bar displays the current screen orientation data that is being emitted from the Device Controller.
95 |
96 | The data is as follows:
97 |
98 | * 's' (or `window.orientation` and `window.screen.orientation.angle`)
99 | * `0` (aka `portrait-primary`): The screen is in its default (natural) orientation.
100 | * `90` (aka `landscape-primary`): The screen is rotated 90 degrees clockwise from its natural orientation.
101 | * `180` (aka `portrait-secondary`): The screen is rotated 180 degrees clockwise from its natural orientation.
102 | * `270` (aka `landscape-secondary`): The screen is rotated 270 degrees clockwise (or, 90 degrees counter-clockwise) from its natural orientation.
103 |
104 | You can change the screen orientation of the device using the button to the right. This rotates the screen in 90 degree increments and simulates the effect of the screen orientation changing from e.g. portrait to landscape.
105 |
106 | If the web app within the Web App Viewport uses the Screen Orientation API to lock the device to a specific screen orientation then the screen rotation button is disabled and is replaced by a lock icon. If or when the emulated web app unlocks the device from a specific screen orientation then the screen rotation button will be enabled again and the lock icon will disappear.
107 |
108 | ##### 4. The Animation Timeline
109 |
110 | The animation timeline lets you record and share pre-defined movements of the emulated device using key frames to mark the waypoints of that movement.
111 |
112 |
113 |
114 | ###### 4.1. Play the current animation timeline
115 |
116 | When the doe emulator loads it will auto-play any animations that have been included in the emulator URL.
117 |
118 | At any time you want to replay the animation timeline you can click this button and the doe emulator will replay the recorded key frame(s).
119 |
120 | When the doe emulator is playing a timeline it will disable buttons within the Emulator Menu Bar and also disable the Device Controller. Once animation ends, the emulator will be reset to the position of the first key frame and any disabled controls will be reenabled.
121 |
122 | ###### 4.2. Key frame(s)
123 |
124 | The current key frames for this animation.
125 |
126 | At any time you can click on one of these items and then, any changes you make to device or screen orientation will be recorded against this key frame.
127 |
128 | ###### 4.3. Add a new key frame
129 |
130 | Clicking this button adds a new key frame to the animation timeline.
131 |
132 | The device and screen orientation of the previous frame will be maintained and you can now change the values of this new frame.
133 |
134 | By clicking the play button the doe emulator will interpolate between these frames to create a smooth transition between the device and screen orientation values between the key frames.
135 |
136 | You can add up to a maximum of 20 key frames within the doe emulator.
137 |
138 | ###### 4.4. Delete all key frames
139 |
140 | This button will remove all key frames, only keeping the first key frame that was created within the animation timeline.
141 |
142 | #### License
143 |
144 | MIT. Copyright © Rich Tibbett.
145 |
--------------------------------------------------------------------------------
/doe.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * doe - The Device and Screen Orientation Emulator
4 | * https://github.com/richtr/doe
5 | *
6 | * A simple, accurate device orientation emulator for use in web browsers
7 | * that do not support device orientation events
8 | * (https://w3c.github.io/deviceorientation/spec-source-orientation.html)
9 | *
10 | * This emulator also supports Screen Orientation API events
11 | * (http://www.w3.org/TR/screen-orientation/)
12 | *
13 | * Copyright: 2015 Rich Tibbett
14 | * License: MIT
15 | *
16 | */
17 |
18 | ( function() {
19 |
20 | var emulatorUrl = new URL( 'https://richtr.github.io/doe/emulator' );
21 |
22 | var Detector = ( function() {
23 |
24 | var detectorLoaded = false;
25 |
26 | return function() {
27 |
28 | if ( detectorLoaded ) return;
29 |
30 | var detectionCheckTimeout = 1000;
31 |
32 | var deviceOrientationCheck = window.setTimeout( function() {
33 |
34 | SweetAlertLoader( function() {
35 | swal( {
36 | title: 'No compass detected.',
37 | text: 'This page is built for browsers that emit hardware sensor events. If you would still like to try this page you can use an emulator.',
38 | type: 'error',
39 | showCancelButton: true,
40 | confirmButtonColor: '#DD6B55',
41 | confirmButtonText: 'Open page in emulator',
42 | cancelButtonText: 'Cancel',
43 | closeOnConfirm: true
44 | },
45 | function() {
46 | // Open the mobile emulator with the current URL
47 | var pageUrl = encodeURIComponent( window.location );
48 | window.location = emulatorUrl.toString() + '/?url=' + pageUrl;
49 | } );
50 | } );
51 |
52 | }, detectionCheckTimeout );
53 |
54 | var isFirstEvent = true;
55 |
56 | window.addEventListener( 'deviceorientation', function check() {
57 | // Discard first event (false positive on Chromium Desktop browsers)
58 | if ( isFirstEvent ) {
59 | isFirstEvent = false;
60 | return;
61 | }
62 |
63 | // Prevent emulator alert from kicking in
64 | window.clearTimeout( deviceOrientationCheck );
65 | // Remove this device orientation check
66 | window.removeEventListener( 'deviceorientation', check, false );
67 | }, false );
68 |
69 | detectorLoaded = true;
70 |
71 | };
72 |
73 | } )();
74 |
75 | var Emulator = ( function() {
76 |
77 | var _this = this;
78 |
79 | var emulatorLoaded = false;
80 |
81 | var actions = {
82 | 'deviceorientation': function( data ) {
83 |
84 | var event = document.createEvent( 'Event' );
85 | event.initEvent( 'deviceorientation', true, true );
86 |
87 | for ( var key in data ) event[ key ] = data[ key ];
88 | event[ 'simulation' ] = true; // add 'simulated event' flag
89 |
90 | window.dispatchEvent( event );
91 |
92 | },
93 | 'screenOrientationChange': function( data ) {
94 |
95 | // Update window.screen.orientation.angle
96 | _this.screenOrientationAPI.update( data );
97 |
98 | }
99 | };
100 |
101 | var listener = function( event ) {
102 |
103 | if ( event.origin !== emulatorUrl.origin ) return;
104 |
105 | var json = JSON.parse( event.data );
106 |
107 | if ( !json.action || !actions[ json.action ] ) return;
108 |
109 | actions[ json.action ]( json.data );
110 |
111 | }.bind( this );
112 |
113 | return function() {
114 |
115 | if ( emulatorLoaded ) return;
116 |
117 | // Set up Screen Orientation API
118 | _this.screenOrientationAPI = new ScreenOrientationAPI();
119 |
120 | // Listen for incoming window messages from parent
121 | window.addEventListener( 'message', listener, false );
122 |
123 | emulatorLoaded = true;
124 |
125 | };
126 |
127 | } )();
128 |
129 | function ScreenOrientationAPI() {
130 |
131 | var _this = this;
132 |
133 | var angleToType = {
134 | '0': 'portrait-primary',
135 | '90': 'landscape-primary',
136 | '180': 'portrait-secondary',
137 | '270': 'landscape-secondary',
138 | // Aliases
139 | '-90': 'landscape-secondary',
140 | '-180': 'portrait-secondary',
141 | '-270': 'landscape-primary',
142 | '360': 'portrait-primary',
143 | };
144 | var typeToAngle = {
145 | 'portrait-primary': 0,
146 | 'portrait-secondary': 180,
147 | 'landscape-primary': 90,
148 | 'landscape-secondary': 270,
149 | // Additional types
150 | 'any': 0,
151 | 'natural': 0,
152 | 'landscape': 90,
153 | 'portrait': 0
154 | };
155 |
156 | var hasScreenOrientationAPI = window.screen && window.screen.orientation ? true : false;
157 |
158 | _this._angle = 0;
159 | _this._type = 'portrait-primary';
160 |
161 | function Emitter() {
162 | var eventTarget = document.createDocumentFragment();
163 |
164 | function delegate( method ) {
165 | this[ method ] = eventTarget[ method ].bind( eventTarget );
166 | }
167 |
168 | [
169 | "addEventListener",
170 | "dispatchEvent",
171 | "removeEventListener"
172 | ].forEach( delegate, this );
173 | }
174 |
175 | function _ScreenOrientation() {
176 | Emitter.call( this );
177 | }
178 |
179 | // Update internal screen orientation and fire the appropriate DOM events
180 | this.update = function( angle ) {
181 |
182 | var _angle = angle % 360;
183 |
184 | // Update internal API values
185 | _this._angle = _angle;
186 | _this._type = angleToType[ _angle ];
187 |
188 | // Update window.orientation
189 | window.orientation = _this._angle;
190 |
191 | // Fire a 'orientationchange' event at window
192 | var event = document.createEvent( 'Event' );
193 | event.initEvent( 'orientationchange', true, true );
194 | window.dispatchEvent( event );
195 |
196 | // Fire a 'change' event at window.screen.orientation
197 | var event = document.createEvent( 'Event' );
198 | event.initEvent( 'change', true, true );
199 | window.screen.orientation.dispatchEvent( event );
200 |
201 | };
202 |
203 | // If browser does not support the Screen Orientation API, add a stub for it
204 | if ( !hasScreenOrientationAPI ) {
205 | if ( !window.screen ) window.screen = {};
206 | if ( !window.screen.orientation ) window.screen.orientation = new _ScreenOrientation();
207 | }
208 |
209 | // Override Screen Orientation API 'angle' built-in getter
210 | window.screen.orientation.__defineGetter__( 'angle', function() {
211 | return _this._angle;
212 | } );
213 |
214 | // Override Screen Orientation API 'type' built-in getter
215 | window.screen.orientation.__defineGetter__( 'type', function() {
216 | return _this._type;
217 | } );
218 |
219 | window.screen.orientation.__proto__.lock = function( val ) {
220 | var p = new Promise( function( resolve, reject ) {
221 |
222 | // Check a valid type is provided
223 | var angle = typeToAngle[ val ];
224 |
225 | if ( angle === undefined || angle === null ) {
226 | window.setTimeout( function() {
227 | reject( "Cannot lock to given screen orientation" ); // reject as invalid
228 | }, 1 );
229 | return;
230 | }
231 |
232 | _this.update( angle );
233 |
234 | window.setTimeout( function() {
235 | resolve();
236 | }, 1 );
237 |
238 | // Lock the screen orientation icon in parent emulator
239 | if ( window.parent ) {
240 |
241 | window.parent.postMessage( JSON.stringify( {
242 | 'action': 'lockScreenOrientation',
243 | 'data': angle
244 | } ), '*' );
245 |
246 | }
247 |
248 | } );
249 |
250 | return p;
251 | };
252 |
253 | window.screen.orientation.__proto__.unlock = function( val ) {
254 | if ( !window.parent ) return;
255 |
256 | window.parent.postMessage( JSON.stringify( {
257 | 'action': 'unlockScreenOrientation'
258 | } ), '*' );
259 | };
260 |
261 | return this;
262 |
263 | }
264 |
265 | var SweetAlertLoader = ( function() {
266 |
267 | var isSWALLoaded = false;
268 |
269 | return function( callback ) {
270 |
271 | if ( isSWALLoaded ) {
272 | // Fire callback after current script runs to completion
273 | window.setTimeout(function() {
274 | callback();
275 | }, 1);
276 | return;
277 | }
278 |
279 | var swalCSSEl = document.createElement( 'link' );
280 | swalCSSEl.href = 'https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css';
281 | swalCSSEl.type = 'text/css';
282 | swalCSSEl.rel = 'stylesheet';
283 | document.getElementsByTagName( 'head' )[ 0 ].appendChild( swalCSSEl );
284 |
285 | var swalJSEl = document.createElement( 'script' );
286 | swalJSEl.src = 'https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js';
287 | swalJSEl.type = 'text/javascript';
288 |
289 | swalJSEl.onload = function() {
290 | callback();
291 | };
292 |
293 | document.getElementsByTagName( 'head' )[ 0 ].appendChild( swalJSEl );
294 |
295 | isSWALLoaded = true;
296 |
297 | }
298 |
299 | } )();
300 |
301 | // Run on start
302 |
303 | var referrerUrl = new URL( document.referrer || 'http:a' );
304 | var referrerPath = referrerUrl.pathname;
305 |
306 | var originsMatch = referrerUrl.origin === emulatorUrl.origin;
307 | var pathsMath = referrerPath.indexOf( emulatorUrl.pathname, 0 ) === 0;
308 | var notScreenEmbedded = referrerPath.indexOf( "/screen", 0 ) < 0;
309 |
310 | if ( originsMatch && pathsMath && notScreenEmbedded ) {
311 | // We have been kicked from the emulator.
312 | // Display an alert that we were kicked.
313 | SweetAlertLoader( function() {
314 | swal( {
315 | title: "Compass detected.",
316 | text: "You have been redirected here from the emulator because your device supports the required hardware sensor events.",
317 | type: "success",
318 | confirmButtonColor: "#638450"
319 | } );
320 | } );
321 | } else if ( window == window.parent ) {
322 | // Check if the current UA supports device orientation events.
323 | // If not, then display a prompt to try this page in the emulator.
324 | if ( document.readyState === 'complete' ) {
325 | Detector();
326 | } else {
327 | window.addEventListener( 'load', Detector, false );
328 | }
329 | } else {
330 | // Run the emulator (listen for proxied events from parent window)
331 | Emulator();
332 | }
333 |
334 | } )();
335 |
--------------------------------------------------------------------------------
/emulator/js/emulator.js:
--------------------------------------------------------------------------------
1 | var selfUrl = new URL( window.location );
2 |
3 | function startEmulator() {
4 |
5 | var controller = document.querySelector( 'iframe#controller' );
6 | var emulatorMenu = document.querySelector( '#emulator' );
7 | var credits = document.querySelector( '#credits' );
8 |
9 | var deviceFrame = document.querySelector( 'iframe#deviceFrame' );
10 |
11 | controller.addEventListener( 'load', function() {
12 | controller.isLoaded = true;
13 |
14 | controller.style.display = 'block';
15 | emulatorMenu.style.display = 'block';
16 | credits.style.display = 'block';
17 | }, false );
18 |
19 | // Load controller
20 | controller.src = '../controller/index.html';
21 |
22 | deviceFrame.addEventListener( 'load', function() {
23 | deviceFrame.isLoaded = true;
24 | deviceFrame.style.display = 'block';
25 | }, false );
26 |
27 | // Load target in screen iframe
28 | deviceFrame.src = "screen.html" + location.search;
29 |
30 | var scaleFactor = 1;
31 |
32 | $( 'button[data-viewport-width]' ).on( 'click', function( e ) {
33 | if ( $( this ).attr( 'data-viewport-width' ) == '100%' ) {
34 | newWidth = '100%';
35 | } else {
36 | newWidth = $( this ).attr( 'data-viewport-width' );
37 | }
38 | if ( $( this ).attr( 'data-viewport-height' ) == '100%' ) {
39 | newHeight = '100%';
40 | } else {
41 | newHeight = $( this ).attr( 'data-viewport-height' );
42 | }
43 | $( 'button[data-viewport-width]' ).removeClass( 'asphalt active' ).addClass( 'charcoal' );
44 | $( this ).addClass( 'asphalt active' ).removeClass( 'charcoal' );
45 | $( '#deviceFrame' ).css( {
46 | 'min-width': newWidth,
47 | 'min-height': newHeight
48 | } );
49 | e.preventDefault();
50 |
51 | var w = parseInt( newWidth, 10 );
52 | var h = parseInt( newHeight, 10 );
53 | var newDimension = 0;
54 |
55 | // Take the larger of the two values
56 | if ( w >= h ) {
57 | newDimension = w;
58 | } else {
59 | newDimension = h;
60 | }
61 |
62 | // Relay new dimensions on to deviceFrame
63 | sendMessage(
64 | deviceFrame, {
65 | 'action': 'updateScreenDimensions',
66 | 'data': {
67 | 'newWidth': newDimension + "px",
68 | 'newHeight': newDimension + "px"
69 | }
70 | },
71 | selfUrl.origin
72 | );
73 |
74 | return false;
75 | } );
76 |
77 | $( 'button.rotate' ).on( 'click', function( e ) {
78 |
79 | var currentRotation = currentScreenOrientation == 0 ? 360 : currentScreenOrientation;
80 |
81 | // Post message to self to update screen orientation
82 | postMessage( JSON.stringify( {
83 | 'action': 'updateScreenOrientation',
84 | 'data': {
85 | 'totalRotation': currentRotation - 90,
86 | 'updateControls': true
87 | }
88 | } ), selfUrl.origin );
89 |
90 | } );
91 |
92 | var deviceScaleValue = $( '#deviceScaleValue' );
93 |
94 | $( '#deviceScaling' ).on( 'input', function( e ) {
95 | scaleFactor = e.target.value;
96 | deviceScaleValue.text( scaleFactor + "x" );
97 | } );
98 |
99 | $( 'button.reset' ).on( 'click', function( e ) {
100 |
101 | // reset the controller
102 | sendMessage(
103 | controller, {
104 | 'action': 'restart'
105 | },
106 | selfUrl.origin
107 | );
108 |
109 | // Update controller rendering
110 | sendMessage(
111 | controller, {
112 | 'action': 'rotateScreen',
113 | 'data': {
114 | 'rotationDiff': currentScreenOrientation,
115 | 'totalRotation': currentScreenOrientation,
116 | 'updateControls': true
117 | }
118 | },
119 | selfUrl.origin
120 | );
121 |
122 | } );
123 |
124 | var orientationAlpha = document.querySelector( 'input#orientationAlpha' );
125 | var orientationBeta = document.querySelector( 'input#orientationBeta' );
126 | var orientationGamma = document.querySelector( 'input#orientationGamma' );
127 |
128 | var currentScreenOrientation = 360;
129 |
130 | var userIsEditing = false;
131 |
132 | function onUserIsEditingStart( e ) {
133 | userIsEditing = true;
134 | }
135 |
136 | function onUserIsEditingEnd( e ) {
137 | var alpha = parseFloat( orientationAlpha.value, 10 );
138 | var beta = parseFloat( orientationBeta.value, 10 );
139 | var gamma = parseFloat( orientationGamma.value, 10 );
140 |
141 | // Fit all inputs within acceptable interval
142 | alpha = alpha % 360;
143 | if ( beta < -180 ) beta = -180;
144 | if ( beta > 180 ) beta = 180;
145 | if ( gamma < -90 ) gamma = -90;
146 | if ( gamma > 90 ) gamma = 90;
147 |
148 | sendMessage(
149 | controller, {
150 | 'action': 'setCoords',
151 | 'data': {
152 | 'alpha': alpha || 0,
153 | 'beta': beta || 0,
154 | 'gamma': gamma || 0
155 | }
156 | },
157 | selfUrl.origin
158 | );
159 |
160 | }
161 |
162 | function stopUserEditing( e ) {
163 | userIsEditing = false;
164 | }
165 |
166 | function stopUserEditingKey( e ) {
167 | var keyCode = e.which || e.keyCode;
168 | if ( keyCode !== 13 ) {
169 | return true;
170 | }
171 | // Force blur when return key is pressed
172 | var target = e.target;
173 | target.blur();
174 | }
175 |
176 | orientationAlpha.addEventListener( 'focus', onUserIsEditingStart, false );
177 | orientationAlpha.addEventListener( 'change', onUserIsEditingEnd, false );
178 | orientationAlpha.addEventListener( 'keypress', stopUserEditingKey, false );
179 | orientationAlpha.addEventListener( 'blur', stopUserEditing, false );
180 |
181 | orientationBeta.addEventListener( 'focus', onUserIsEditingStart, false );
182 | orientationBeta.addEventListener( 'change', onUserIsEditingEnd, false );
183 | orientationBeta.addEventListener( 'keypress', stopUserEditingKey, false );
184 | orientationBeta.addEventListener( 'blur', stopUserEditing, false );
185 |
186 | orientationGamma.addEventListener( 'focus', onUserIsEditingStart, false );
187 | orientationGamma.addEventListener( 'change', onUserIsEditingEnd, false );
188 | orientationGamma.addEventListener( 'keypress', stopUserEditingKey, false );
189 | orientationGamma.addEventListener( 'blur', stopUserEditing, false );
190 |
191 | var screenOrientationEl = document.querySelector( '#screenOrientation' );
192 |
193 | var actions = {
194 | 'newData': function( data ) {
195 |
196 | // Print deviceorientation data values in GUI
197 | if ( !userIsEditing ) {
198 | orientationAlpha.value = printDataValue( data.alpha );
199 | orientationBeta.value = printDataValue( data.beta );
200 | orientationGamma.value = printDataValue( data.gamma );
201 | }
202 |
203 | // Indicate that certain values are shown rounded for display purposes
204 | if ( orientationBeta.textContent === "180" ) orientationBeta.textContent += "*";
205 | if ( orientationGamma.textContent === "90" ) orientationGamma.textContent += "*";
206 |
207 | var roll = data[ 'roll' ] || 0;
208 | delete data[ 'roll' ]; // remove roll attribute from json
209 |
210 | // Post deviceorientation data to deviceFrame window
211 | sendMessage(
212 | deviceFrame, {
213 | 'action': 'deviceorientation',
214 | 'data': data
215 | },
216 | selfUrl.origin
217 | );
218 |
219 | // Apply roll compensation to deviceFrame
220 | deviceFrame.style.webkitTransform = deviceFrame.style.msTransform = deviceFrame.style.transform = 'rotate(' + ( roll - currentScreenOrientation ) + 'deg) scale(' + scaleFactor + ')';
221 |
222 | },
223 | 'updateScreenOrientation': function( data ) {
224 |
225 | var requestedScreenOrientation = data.totalRotation % 360;
226 | var updateControls = data.updateControls;
227 |
228 | // Calculate rotation difference
229 | var currentRotation = currentScreenOrientation == 0 ? 360 : currentScreenOrientation;
230 |
231 | var rotationDiff = currentRotation - requestedScreenOrientation;
232 |
233 | // Update controller rendering
234 | sendMessage(
235 | controller, {
236 | 'action': 'rotateScreen',
237 | 'data': {
238 | 'rotationDiff': -rotationDiff,
239 | 'totalRotation': requestedScreenOrientation,
240 | 'updateControls': updateControls
241 | }
242 | },
243 | selfUrl.origin
244 | );
245 |
246 | // Notify emulated page that screen orientation has changed
247 | sendMessage(
248 | deviceFrame, {
249 | 'action': 'screenOrientationChange',
250 | 'data': 360 - requestedScreenOrientation
251 | },
252 | selfUrl.origin
253 | );
254 |
255 | if ( ( ( currentRotation / 90 ) % 2 ) !== ( ( requestedScreenOrientation / 90 ) % 2 ) ) {
256 |
257 | $( 'button[data-rotate=true]' ).each( function() {
258 | width = $( this ).attr( 'data-viewport-width' );
259 | height = $( this ).attr( 'data-viewport-height' );
260 | $( this ).attr( 'data-viewport-width', height );
261 | $( this ).attr( 'data-viewport-height', width );
262 | if ( $( this ).hasClass( 'active' ) ) {
263 | $( this ).trigger( 'click' );
264 | }
265 | } );
266 |
267 | }
268 |
269 | screenOrientationEl.textContent = ( 360 - requestedScreenOrientation ) % 360;
270 |
271 | // Update current screen orientation
272 | currentScreenOrientation = requestedScreenOrientation;
273 |
274 | },
275 | 'lockScreenOrientation': function( data ) {
276 |
277 | // Post message to self to update screen orientation
278 | postMessage( JSON.stringify( {
279 | 'action': 'updateScreenOrientation',
280 | 'data': {
281 | 'totalRotation': 360 - data,
282 | 'updateControls': true
283 | }
284 | } ), selfUrl.origin );
285 |
286 | $( 'button.rotate' ).prop( "disabled", true ).attr( "title", "Screen Rotation is locked by page" );
287 | $( 'i', 'button.rotate' ).addClass( 'icon-lock' ).removeClass( 'icon-rotate-left' );
288 |
289 | },
290 | 'unlockScreenOrientation': function( data ) {
291 |
292 | $( 'button.rotate' ).attr( "title", "Rotate the Screen" ).prop( "disabled", false );
293 | $( 'i', 'button.rotate' ).addClass( 'icon-rotate-left' ).removeClass( 'icon-lock' );
294 |
295 | }
296 | }
297 |
298 | // Relay deviceorientation events on to content iframe
299 | window.addEventListener( 'message', function( event ) {
300 |
301 | if ( event.origin != selfUrl.origin ) return;
302 |
303 | var json = JSON.parse( event.data );
304 |
305 | if ( !json.action || !actions[ json.action ] ) return;
306 |
307 | actions[ json.action ]( json.data );
308 |
309 | }, false );
310 |
311 | }
312 |
--------------------------------------------------------------------------------
/controller/js/DeviceOrientationEmulatorControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * -------
3 | * Modified version of threeVR (https://github.com/richtr/threeVR)
4 | * -------
5 | *
6 | * Author: Rich Tibbett (http://github.com/richtr)
7 | * License: The MIT License
8 | *
9 | **/
10 |
11 | var DeviceOrientationEmulatorControls = function( object, domElement ) {
12 |
13 | this.object = object;
14 | this.element = domElement || document;
15 |
16 | this.enableManualDrag = true; // enable manual user drag override control by default
17 | this.enableManualZoom = true; // enable manual user zoom override control by default
18 |
19 | this.useQuaternions = true; // use quaternions for orientation calculation by default
20 |
21 | // Manual rotate override components
22 | var startX = 0,
23 | startY = 0,
24 | currentX = 0,
25 | currentY = 0,
26 | scrollSpeedX, scrollSpeedY,
27 | tmpQuat = new THREE.Quaternion();
28 |
29 | // Manual zoom override components
30 | var zoomStart = 1,
31 | zoomCurrent = 1,
32 | zoomP1 = new THREE.Vector2(),
33 | zoomP2 = new THREE.Vector2(),
34 | tmpFOV;
35 |
36 | var CONTROLLER_STATE = {
37 | AUTO: 0,
38 | MANUAL_ROTATE: 1,
39 | MANUAL_ZOOM: 2
40 | };
41 |
42 | var appState = CONTROLLER_STATE.AUTO;
43 |
44 | var CONTROLLER_EVENT = {
45 | MANUAL_CONTROL: 'userinteraction', // userinteractionstart, userinteractionend
46 | ZOOM_CONTROL: 'zoom', // zoomstart, zoomend
47 | ROTATE_CONTROL: 'rotate', // rotatestart, rotateend
48 | };
49 |
50 | // Consistent Object Field-Of-View fix components
51 | var startClientHeight = window.innerHeight,
52 | startFOVFrustrumHeight = 2000 * Math.tan( THREE.Math.degToRad( ( this.object.fov || 75 ) / 2 ) ),
53 | relativeFOVFrustrumHeight, relativeVerticalFOV;
54 |
55 | var deviceQuat = new THREE.Quaternion();
56 |
57 | var fireEvent = function() {
58 | var eventData;
59 |
60 | return function( name ) {
61 | window.setTimeout(function() {
62 | eventData = arguments || {};
63 |
64 | eventData.type = name;
65 | eventData.target = this;
66 |
67 | this.dispatchEvent( eventData );
68 | }.bind( this ), 1);
69 | }.bind( this );
70 | }.bind( this )();
71 |
72 | this.constrainObjectFOV = function() {
73 | relativeFOVFrustrumHeight = startFOVFrustrumHeight * ( window.innerHeight / startClientHeight );
74 |
75 | relativeVerticalFOV = THREE.Math.radToDeg( 2 * Math.atan( relativeFOVFrustrumHeight / 2000 ) );
76 |
77 | this.object.fov = relativeVerticalFOV;
78 | }.bind( this );
79 |
80 | this.onDocumentMouseDown = function( event ) {
81 | event.preventDefault();
82 |
83 | appState = CONTROLLER_STATE.MANUAL_ROTATE;
84 |
85 | tmpQuat.copy( this.object.quaternion );
86 |
87 | startX = currentX = event.pageX;
88 | startY = currentY = event.pageY;
89 |
90 | // Set consistent scroll speed based on current viewport width/height
91 | scrollSpeedX = ( 1200 / window.innerWidth ) * 0.2;
92 | scrollSpeedY = ( 800 / window.innerHeight ) * 0.2;
93 |
94 | this.element.addEventListener( 'mousemove', this.onDocumentMouseMove, false );
95 | this.element.addEventListener( 'mouseup', this.onDocumentMouseUp, false );
96 | this.element.addEventListener( 'mouseout', this.onDocumentMouseUp, false );
97 |
98 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' );
99 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'start' );
100 | }.bind( this );
101 |
102 | this.onDocumentMouseMove = function( event ) {
103 | currentX = event.pageX;
104 | currentY = event.pageY;
105 | }.bind( this );
106 |
107 | this.onDocumentMouseUp = function( event ) {
108 | this.element.removeEventListener( 'mousemove', this.onDocumentMouseMove, false );
109 | this.element.removeEventListener( 'mouseup', this.onDocumentMouseUp, false );
110 | this.element.removeEventListener( 'mouseout', this.onDocumentMouseUp, false );
111 |
112 | appState = CONTROLLER_STATE.AUTO;
113 |
114 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' );
115 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'end' );
116 | }.bind( this );
117 |
118 | this.onDocumentTouchStart = function( event ) {
119 | event.preventDefault();
120 | event.stopPropagation();
121 |
122 | switch ( event.touches.length ) {
123 | case 1: // ROTATE
124 |
125 | appState = CONTROLLER_STATE.MANUAL_ROTATE;
126 |
127 | tmpQuat.copy( this.object.quaternion );
128 |
129 | startX = currentX = event.touches[ 0 ].pageX;
130 | startY = currentY = event.touches[ 0 ].pageY;
131 |
132 | // Set consistent scroll speed based on current viewport width/height
133 | scrollSpeedX = ( 1200 / window.innerWidth ) * 0.1;
134 | scrollSpeedY = ( 800 / window.innerHeight ) * 0.1;
135 |
136 | this.element.addEventListener( 'touchmove', this.onDocumentTouchMove, false );
137 | this.element.addEventListener( 'touchend', this.onDocumentTouchEnd, false );
138 |
139 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' );
140 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'start' );
141 |
142 | break;
143 |
144 | case 2: // ZOOM
145 |
146 | appState = CONTROLLER_STATE.MANUAL_ZOOM;
147 |
148 | tmpFOV = this.object.fov;
149 |
150 | zoomP1.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
151 | zoomP2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY );
152 |
153 | zoomStart = zoomCurrent = zoomP1.distanceTo( zoomP2 );
154 |
155 | this.element.addEventListener( 'touchmove', this.onDocumentTouchMove, false );
156 | this.element.addEventListener( 'touchend', this.onDocumentTouchEnd, false );
157 |
158 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' );
159 | fireEvent( CONTROLLER_EVENT.ZOOM_CONTROL + 'start' );
160 |
161 | break;
162 | }
163 | }.bind( this );
164 |
165 | this.onDocumentTouchMove = function( event ) {
166 | switch ( event.touches.length ) {
167 | case 1:
168 | currentX = event.touches[ 0 ].pageX;
169 | currentY = event.touches[ 0 ].pageY;
170 | break;
171 |
172 | case 2:
173 | zoomP1.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
174 | zoomP2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY );
175 | break;
176 | }
177 | }.bind( this );
178 |
179 | this.onDocumentTouchEnd = function( event ) {
180 | this.element.removeEventListener( 'touchmove', this.onDocumentTouchMove, false );
181 | this.element.removeEventListener( 'touchend', this.onDocumentTouchEnd, false );
182 |
183 | if ( appState === CONTROLLER_STATE.MANUAL_ROTATE ) {
184 |
185 | appState = CONTROLLER_STATE.AUTO; // reset control state
186 |
187 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' );
188 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'end' );
189 |
190 | } else if ( appState === CONTROLLER_STATE.MANUAL_ZOOM ) {
191 |
192 | this.constrainObjectFOV(); // re-instate original object FOV
193 |
194 | appState = CONTROLLER_STATE.AUTO; // reset control state
195 |
196 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' );
197 | fireEvent( CONTROLLER_EVENT.ZOOM_CONTROL + 'end' );
198 |
199 | }
200 | }.bind( this );
201 |
202 | this.updateManualMove = function() {
203 |
204 | var lat, lon;
205 | var phi, theta;
206 |
207 | var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );
208 |
209 | var rotQuat = new THREE.Quaternion();
210 | var objQuat = new THREE.Quaternion();
211 |
212 | var tmpZ, objZ, realZ;
213 |
214 | var zoomFactor, minZoomFactor = 1; // maxZoomFactor = Infinity
215 |
216 | return function() {
217 |
218 | if ( appState === CONTROLLER_STATE.MANUAL_ROTATE ) {
219 |
220 | lat = - ( startY - currentY ) * scrollSpeedY;
221 | lon = - ( startX - currentX ) * scrollSpeedX;
222 |
223 | phi = THREE.Math.degToRad( lat );
224 | theta = THREE.Math.degToRad( lon );
225 |
226 | // Reset objQuat
227 | objQuat.set(0,0,0,1);
228 |
229 | // Apply y-based mouse rotation
230 | rotQuat.set( 0, Math.sin( theta / 2 ), 0, Math.cos( theta / 2 ) );
231 | objQuat.multiply( rotQuat );
232 |
233 | // Apply z-based mouse rotation
234 | rotQuat.set( Math.sin( phi / 2 ), 0, 0, Math.cos( phi / 2 ) );
235 | objQuat.multiply( rotQuat );
236 |
237 | // Apply existing object rotation to calculated mouse rotation
238 | objQuat.multiply( tmpQuat );
239 |
240 | this.object.quaternion.copy( objQuat );
241 |
242 | } else if ( appState === CONTROLLER_STATE.MANUAL_ZOOM ) {
243 |
244 | zoomCurrent = zoomP1.distanceTo( zoomP2 );
245 |
246 | zoomFactor = zoomStart / zoomCurrent;
247 |
248 | if ( zoomFactor <= minZoomFactor ) {
249 |
250 | this.object.fov = tmpFOV * zoomFactor;
251 |
252 | this.object.updateProjectionMatrix();
253 |
254 | }
255 |
256 | // Add device's current z-axis rotation
257 |
258 | if ( deviceQuat ) {
259 |
260 | tmpZ = rotation.setFromQuaternion( tmpQuat, 'YXZ' ).z;
261 | realZ = rotation.setFromQuaternion( deviceQuat, 'YXZ' ).z;
262 |
263 | rotQuat.set( 0, 0, Math.sin( ( realZ - tmpZ ) / 2 ), Math.cos( ( realZ - tmpZ ) / 2 ) );
264 |
265 | tmpQuat.multiply( rotQuat );
266 |
267 | this.object.quaternion.copy( tmpQuat );
268 |
269 | }
270 |
271 | }
272 |
273 | };
274 |
275 | }();
276 |
277 | this.update = function() {
278 | if ( appState !== CONTROLLER_STATE.AUTO ) {
279 | this.updateManualMove();
280 | }
281 | };
282 |
283 | this.updateScreenOrientation = function( diffRotation ) {
284 | var screenTransform = new THREE.Quaternion();
285 | var minusHalfAngle = 0;
286 |
287 | // Add new screen rotation
288 | minusHalfAngle = -( THREE.Math.degToRad( diffRotation ) ) / 2;
289 | screenTransform.set( 0, 0, Math.sin( minusHalfAngle ), Math.cos( minusHalfAngle ) );
290 | this.object.quaternion.multiply( screenTransform );
291 | };
292 |
293 | this.connect = function() {
294 | window.addEventListener( 'resize', this.constrainObjectFOV, false );
295 |
296 | this.element.addEventListener( 'mousedown', this.onDocumentMouseDown, false );
297 | this.element.addEventListener( 'touchstart', this.onDocumentTouchStart, false );
298 |
299 | this.element.style.cursor = 'move';
300 | };
301 |
302 | this.disconnect = function() {
303 | window.removeEventListener( 'resize', this.constrainObjectFOV, false );
304 |
305 | this.element.removeEventListener( 'mousedown', this.onDocumentMouseDown, false );
306 | this.element.removeEventListener( 'touchstart', this.onDocumentTouchStart, false );
307 |
308 | this.element.style.cursor = 'not-allowed';
309 | };
310 |
311 | };
312 |
313 | DeviceOrientationEmulatorControls.prototype = Object.create( THREE.EventDispatcher.prototype );
314 |
--------------------------------------------------------------------------------
/emulator/js/timeliner.js:
--------------------------------------------------------------------------------
1 | function EmulatorTimeline( alpha, beta, gamma, screen ) {
2 |
3 | var _frames = [];
4 |
5 | this.import = function( frames ) {
6 | _frames = frames;
7 | };
8 |
9 | this.getAll = function( index ) {
10 | return _frames;
11 | };
12 |
13 | this.get = function( index ) {
14 | return _frames[ index ];
15 | };
16 |
17 | this.set = function( obj, duration, offset, index ) {
18 |
19 | var frame = {
20 | 'type': 0, // FIX TO POSITION
21 | 'duration': duration || 1,
22 | 'offset': offset || 0,
23 | 'data': obj || {}
24 | };
25 |
26 | if ( index !== undefined && index !== null ) {
27 | // Update an existing animation frame
28 | _frames[ index ] = frame;
29 | } else {
30 | // Append a new animation frame
31 | _frames.push( frame );
32 | }
33 |
34 | return this;
35 |
36 | };
37 |
38 | this.length = function() {
39 | return _frames.length;
40 | };
41 |
42 | this.animate = function( obj, duration, offset, index ) {
43 |
44 | var frame = {
45 | 'type': 1, // ANIMATE TO POSITION
46 | 'duration': duration || 1,
47 | 'offset': offset || 0,
48 | 'data': obj || {}
49 | };
50 |
51 | if ( index !== undefined && index !== null ) {
52 | // Update an existing animation frame
53 | _frames[ index ] = frame;
54 | } else {
55 | // Append a new animation frame
56 | _frames.push( frame );
57 | }
58 |
59 | return this;
60 |
61 | };
62 |
63 | this.start = function() {
64 |
65 | sendMessage(
66 | controller, {
67 | 'action': 'playback',
68 | 'data': {
69 | 'frames': _frames
70 | }
71 | },
72 | selfUrl.origin
73 | );
74 |
75 | return this;
76 |
77 | };
78 |
79 | // Set initial device orientation frame data
80 | var data = {
81 | 'alpha': alpha || 0,
82 | 'beta': beta || 0,
83 | 'gamma': gamma || 0,
84 | 'screen': screen || 0
85 | };
86 |
87 | this.set( data, 1, 0 );
88 |
89 | };
90 |
91 | EmulatorTimeline.prototype = {
92 |
93 | 'constructor': EmulatorTimeline,
94 |
95 | // Static method
96 | 'compress': function( framesOriginal ) {
97 | var framesCompressed = [];
98 |
99 | for ( var i = 0, l = framesOriginal.length; i < l; i++ ) {
100 | framesCompressed.push( [
101 | framesOriginal[ i ].type,
102 | framesOriginal[ i ].duration,
103 | framesOriginal[ i ].offset,
104 | framesOriginal[ i ].data.alpha,
105 | framesOriginal[ i ].data.beta,
106 | framesOriginal[ i ].data.gamma,
107 | framesOriginal[ i ].data.screen
108 | ] );
109 | }
110 |
111 | return framesCompressed;
112 | },
113 |
114 | 'uncompress': function( framesCompressed ) {
115 | var framesOriginal = [];
116 |
117 | for ( var i = 0, l = framesCompressed.length; i < l; i++ ) {
118 | var data = {
119 | 'type': framesCompressed[ i ][ 0 ],
120 | 'duration': framesCompressed[ i ][ 1 ],
121 | 'offset': framesCompressed[ i ][ 2 ],
122 | 'data': {
123 | 'alpha': framesCompressed[ i ][ 3 ],
124 | 'beta': framesCompressed[ i ][ 4 ],
125 | 'gamma': framesCompressed[ i ][ 5 ],
126 | 'screen': framesCompressed[ i ][ 6 ]
127 | }
128 | };
129 |
130 | framesOriginal.push( data );
131 | }
132 |
133 | return framesOriginal;
134 | }
135 |
136 | };
137 |
138 | // Create a new emulator timeline
139 | var timeline = new EmulatorTimeline( 0, 0, 0, 0 );
140 |
141 | var activeFrameIndex = 0;
142 | var _lastData = {};
143 |
144 | var actions = {
145 | 'connect': function( data ) {
146 |
147 | $( 'button#play' ).on( 'click', function() {
148 | $( 'button[data-frame-number=0]' ).removeClass( 'charcoal' ).addClass( 'asphalt active' );
149 |
150 | $( 'button[data-frame-number=0]' ).trigger( 'click' );
151 |
152 | timeline.start();
153 | } );
154 |
155 | $( 'body' ).on( 'click', 'button[data-frame-number]', function() {
156 |
157 | activeFrameIndex = $( this ).attr( 'data-frame-number' );
158 |
159 | var activeFrame = timeline.get( activeFrameIndex );
160 |
161 | sendMessage(
162 | controller, {
163 | 'action': 'setCoords',
164 | 'data': activeFrame.data
165 | },
166 | selfUrl.origin
167 | );
168 |
169 | // Post message to self to update screen orientation
170 | postMessage( JSON.stringify( {
171 | 'action': 'updateScreenOrientation',
172 | 'data': {
173 | 'totalRotation': ( 360 - activeFrame.data.screen ) % 360,
174 | 'updateControls': false
175 | }
176 | } ), selfUrl.origin );
177 |
178 | $( 'button.active[data-frame-number]' ).removeClass( 'asphalt active' ).addClass( 'charcoal' );
179 | $( 'button[data-frame-number=' + activeFrameIndex + ']' ).removeClass( 'charcoal' ).addClass( 'asphalt active' );
180 |
181 | } );
182 |
183 | $( 'button#clearTimeline' ).on( 'click', function() {
184 |
185 | // Trash all frame buttons except the first one
186 | $( 'button[data-frame-number]' ).not( ':first' ).remove();
187 |
188 | var startFrame = timeline.get( 0 );
189 | // Reset timeline
190 | timeline.import( [ startFrame ] );
191 |
192 | // Focus the first frame
193 | $( 'button[data-frame-number=0]' ).trigger( 'click' );
194 |
195 | } );
196 |
197 | $( 'button#addNewFrame' ).on( 'click', function() {
198 |
199 | var newFrameId = timeline.length();
200 |
201 | if ( timeline.get( newFrameId ) == undefined ) {
202 |
203 | // Use last known device orientation values to initialize new animation frame
204 | var data = {
205 | alpha: printDataValue( _lastData.alpha ),
206 | beta: printDataValue( _lastData.beta ),
207 | gamma: printDataValue( _lastData.gamma ),
208 | screen: _lastData.screen
209 | };
210 |
211 | timeline.animate( data, 1, 0 );
212 |
213 | }
214 |
215 | // Create and add a new frame button to GUI
216 | $( '#frame-group' ).append(
217 | $( '' ).append(
218 | $( '' )
219 | .attr( 'data-frame-number', newFrameId )
220 | .attr( 'data-disable-during-playback', 'true' )
221 | .text( newFrameId + 1 )
222 | )
223 | );
224 |
225 | // Highlight new frame
226 | $( 'button[data-frame-number=' + newFrameId + ']' ).trigger( 'click' );
227 |
228 | // Don't allow more than 20 frames
229 | if ( newFrameId >= 19 ) {
230 |
231 | $( 'button#addNewFrame' ).attr( 'disabled', 'disabled' );
232 |
233 | }
234 |
235 | } );
236 |
237 | // Tell controller to start rendering
238 | sendMessage(
239 | controller, {
240 | 'action': 'start'
241 | },
242 | selfUrl.origin
243 | );
244 |
245 | var urlHash = selfUrl.hash;
246 |
247 | // Parse provided JSON animation hash object (if any)
248 | if ( urlHash && urlHash.length > 3 ) {
249 | // Remove leading '#'
250 | var jsonBase64 = urlHash.substring( 1 );
251 |
252 | // Base64 decode this data
253 | var jsonStr = window.atob( jsonBase64 );
254 |
255 | try {
256 | var json = JSON.parse( "{\"d\": " + jsonStr + " }" );
257 |
258 | // 'Unzip' the data
259 | var frames = EmulatorTimeline.prototype.uncompress( json.d );
260 |
261 | if ( frames && frames.length > 0 ) {
262 |
263 | // Create the correct number of animation frame buttons
264 | for ( var i = 1; i < frames.length; i++ ) {
265 | $( 'button#addNewFrame' ).trigger( 'click' );
266 | }
267 |
268 | // Import json as the emulator timeline
269 | timeline.import( frames );
270 |
271 | // Focus the first frame
272 | $( 'button[data-frame-number=0]' ).trigger( 'click' );
273 |
274 | // Update onscreen coords
275 | sendMessage(
276 | controller, {
277 | 'action': 'setCoords',
278 | 'data': {
279 | 'alpha': frames[ 0 ].data.alpha || 0,
280 | 'beta': frames[ 0 ].data.beta || 0,
281 | 'gamma': frames[ 0 ].data.gamma || 0
282 | }
283 | },
284 | selfUrl.origin
285 | );
286 |
287 | // Post message to self to then update controller screen orientation
288 | postMessage( JSON.stringify( {
289 | 'action': 'updateScreenOrientation',
290 | 'data': {
291 | 'totalRotation': ( 360 - frames[ 0 ].data.screen ) % 360,
292 | 'updateControls': false
293 | }
294 | } ), selfUrl.origin );
295 |
296 | // Start whatever animation was loaded when page loads
297 | window.setTimeout(function() {
298 | timeline.start();
299 | }, 500); // delay until setCoords and updateScreenOrientation above complete
300 |
301 | }
302 | } catch ( e ) {
303 | console.log( e );
304 | }
305 |
306 | }
307 |
308 | $( '#timeline' ).css( {
309 | 'display': 'block'
310 | } );
311 |
312 | },
313 | 'newData': function( data ) {
314 | var _data = {
315 | alpha: printDataValue( data.alpha ),
316 | beta: printDataValue( data.beta ),
317 | gamma: printDataValue( data.gamma ),
318 | screen: data.screen
319 | }
320 |
321 | if ( _data.alpha == _lastData.alpha && _data.beta == _lastData.beta && _data.gamma == _lastData.gamma && _data.screen == _lastData.screen ) return;
322 |
323 | // If this data applies to the first frame or a screen orientation change
324 | // is being observed then use .set instead of .animate!
325 | if ( activeFrameIndex === 0 || _data.screen !== _lastData.screen ) {
326 | timeline.set( _data, _data.screen !== _lastData.screen ? 1 : 0.5, 0, activeFrameIndex );
327 | } else {
328 | timeline.animate( _data, 1, 0, activeFrameIndex );
329 | }
330 |
331 | _lastData = _data; // store for next loop
332 | },
333 | 'updatePosition': function( data ) {
334 | window.setTimeout( function() {
335 | // 'Zip' the current timeline data
336 | var framesCompressed = EmulatorTimeline.prototype.compress( timeline.getAll() );
337 |
338 | // Stringify the data object
339 | var jsonStr = JSON.stringify( framesCompressed );
340 |
341 | // Base64 encode the data object
342 | var jsonBase64 = window.btoa( jsonStr );
343 |
344 | // Replace current URL hash with compressed, stringified, encoded data!
345 | selfUrl.hash = '#' + jsonBase64;
346 | replaceURL( selfUrl );
347 | }, 150 );
348 | },
349 | 'playbackStarted': function( data ) {
350 |
351 | // Disable buttons
352 | $( '[data-disable-during-playback]' ).each( function() {
353 | $( this ).attr( 'disabled', 'disabled' );
354 | } );
355 |
356 | },
357 | 'playbackTransition': function( data ) {
358 |
359 | // Disable the previous frame if we have one
360 | if ( data > 0 ) {
361 | var previousFrameButton = $( 'button[data-frame-number=' + ( data - 1 ) + ']' );
362 |
363 | previousFrameButton.removeClass( 'asphalt active' )
364 | previousFrameButton.addClass( 'charcoal' );
365 | previousFrameButton.attr( 'disabled', 'disabled' );
366 | }
367 |
368 | $( 'button[data-frame-number=' + data + ']' ).removeAttr( 'disabled' ).trigger( 'click' );
369 |
370 | },
371 | 'playbackEnded': function( data ) {
372 |
373 | // Re-enable buttons
374 | $( '[data-disable-during-playback]' ).each( function() {
375 | $( this ).removeAttr( 'disabled' );
376 | } );
377 |
378 | $( 'button[data-frame-number=0]' ).trigger( 'click' );
379 |
380 | }
381 | };
382 |
383 | window.addEventListener( 'message', function( event ) {
384 |
385 | if ( event.origin != selfUrl.origin ) return;
386 |
387 | var json = JSON.parse( event.data );
388 |
389 | if ( !json.action || !actions[ json.action ] ) return;
390 |
391 | actions[ json.action ]( json.data );
392 |
393 | }, false );
394 |
--------------------------------------------------------------------------------
/controller/js/app.js:
--------------------------------------------------------------------------------
1 | var APP = {
2 |
3 | Player: function() {
4 |
5 | var scope = this;
6 |
7 | var loader = new THREE.ObjectLoader();
8 | var camera, scene, renderer;
9 |
10 | var controls;
11 |
12 | var events = {};
13 |
14 | this.dom = undefined;
15 |
16 | this.width = 500;
17 | this.height = 500;
18 |
19 | var prevTime, request;
20 |
21 | var euler = new THREE.Euler();
22 | var deviceOrientation = new FULLTILT.Euler();
23 |
24 | var worldQuat = new THREE.Quaternion( -Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) );
25 |
26 | var camQuat = new THREE.Quaternion();
27 |
28 | var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );
29 | var rotQuat = new THREE.Quaternion();
30 |
31 | var tweenInProgress = false;
32 |
33 | this.load = function( json ) {
34 |
35 | renderer = new THREE.WebGLRenderer( {
36 | antialias: true
37 | } );
38 | renderer.setClearColor( 0xFFFFFF, 1 );
39 | renderer.setPixelRatio( window.devicePixelRatio );
40 | this.dom = renderer.domElement;
41 |
42 | this.setScene( loader.parse( json.scene ) );
43 | this.setCamera( loader.parse( json.camera ) );
44 |
45 | events = {
46 | update: []
47 | };
48 |
49 | for ( var uuid in json.scripts ) {
50 |
51 | var object = scene.getObjectByProperty( 'uuid', uuid, true );
52 |
53 | var scripts = json.scripts[ uuid ];
54 |
55 | for ( var i = 0; i < scripts.length; i++ ) {
56 |
57 | var script = scripts[ i ];
58 |
59 | var functions = ( new Function( 'player, scene, update', script.source + '\nreturn { update: update };' ).bind( object ) )( this, scene );
60 |
61 | for ( var name in functions ) {
62 |
63 | if ( functions[ name ] === undefined ) continue;
64 |
65 | if ( events[ name ] === undefined ) {
66 |
67 | console.warn( 'APP.Player: event type not supported (', name, ')' );
68 | continue;
69 |
70 | }
71 |
72 | events[ name ].push( functions[ name ].bind( object ) );
73 |
74 | }
75 |
76 | }
77 |
78 | }
79 |
80 | // Rotate the phone group in the scene, not the camera as usual
81 | var phoneGroupObj = scene.getObjectByProperty( 'uuid', 'D5083881-7A5B-40AF-8A5F-19F401AF1C70', true );
82 |
83 | // Set up device orientation emulator controls
84 | controls = new DeviceOrientationEmulatorControls( phoneGroupObj, scope.dom );
85 | controls.enableManualZoom = false;
86 | controls.connect();
87 |
88 | // Tell parent window to update its URL hash whenever interfaction with controls ends
89 | controls.addEventListener( 'userinteractionend', function() {
90 | if ( window.parent ) {
91 |
92 | sendMessage(
93 | window.parent, {
94 | 'action': 'updatePosition'
95 | }
96 | );
97 |
98 | }
99 | }, false );
100 |
101 | };
102 |
103 | this.setCamera = function( value ) {
104 |
105 | camera = value;
106 | camera.aspect = this.width / this.height;
107 | camera.updateProjectionMatrix();
108 |
109 | };
110 |
111 | this.setScene = function( value ) {
112 |
113 | scene = value;
114 |
115 | };
116 |
117 | this.setSize = function( width, height ) {
118 |
119 | if ( renderer._fullScreen ) return;
120 |
121 | this.width = width;
122 | this.height = height;
123 |
124 | camera.aspect = this.width / this.height;
125 | camera.updateProjectionMatrix();
126 |
127 | renderer.setSize( width, height );
128 |
129 | };
130 |
131 | this.setManualOrientation = ( function() {
132 |
133 | var _q = new THREE.Quaternion();
134 |
135 | return function( alpha, beta, gamma ) {
136 |
137 | var _x = THREE.Math.degToRad( beta || 0 );
138 | var _y = THREE.Math.degToRad( alpha || 0 );
139 | var _z = THREE.Math.degToRad( gamma || 0 );
140 |
141 | euler.set( _x, _y, -_z, 'YXZ' );
142 |
143 | // Apply provided deviceorientation values to controller
144 | _q.setFromEuler( euler );
145 | _q.multiply( worldQuat );
146 |
147 | controls.object.quaternion.copy( _q );
148 |
149 | };
150 |
151 | } )();
152 |
153 | this.playback = ( function() {
154 |
155 | var source, destination;
156 | var _this;
157 |
158 | var _a0, _b0, _g0;
159 |
160 | return function( data ) {
161 |
162 | _this = this;
163 |
164 | // Disconnect controls
165 | controls.disconnect();
166 |
167 | // Store original device orientation values
168 | _a0 = deviceOrientation.alpha;
169 | _b0 = deviceOrientation.beta;
170 | _g0 = deviceOrientation.gamma;
171 |
172 | var frameNumber = 0;
173 |
174 | sendMessage(
175 | window.parent, {
176 | 'action': 'playbackStarted'
177 | }
178 | );
179 |
180 | // Tween through each of our animation frames
181 | data.frames.reduce( function( chain, frame ) {
182 | // Add these actions to the end of the promise chain
183 | return chain.then( function() {
184 | sendMessage(
185 | window.parent, {
186 | 'action': 'playbackTransition',
187 | 'data': frameNumber++
188 | }
189 | );
190 |
191 | if ( frame.type === 0 ) { // SET
192 | return _this.set( frame );
193 | } else { // ANIMATION
194 | return _this.tween( frame );
195 | }
196 | } );
197 | }, Promise.resolve() ).then( function() {
198 | // Rollback to original device orientation values
199 | window.setTimeout( function() {
200 | sendMessage(
201 | window.parent, {
202 | 'action': 'playbackEnded'
203 | }
204 | );
205 |
206 | _this.setManualOrientation( _a0, _b0, _g0 );
207 |
208 | // Re-connect controls
209 | controls.connect();
210 | }, 1000 );
211 | } ).catch( function() {
212 | // Clean-up on error
213 | sendMessage(
214 | window.parent, {
215 | 'action': 'playbackEnded'
216 | }
217 | );
218 |
219 | _this.setManualOrientation( _a0, _b0, _g0 );
220 |
221 | // Re-connect controls
222 | controls.connect();
223 | } );
224 |
225 | };
226 |
227 | } )();
228 |
229 | this.set = ( function() {
230 |
231 | var _this;
232 |
233 | var waitTime, playTime;
234 |
235 | return function( frame ) {
236 |
237 | _this = this;
238 |
239 | var setPromise = new Promise( function( resolve, reject ) {
240 |
241 | waitTime = frame.offset * 1000;
242 | playTime = frame.duration * 1000;
243 |
244 | window.setTimeout( function() {
245 |
246 | _this.setManualOrientation( frame.data.alpha, frame.data.beta, frame.data.gamma );
247 |
248 | window.setTimeout( function() {
249 | resolve(); // this Promise can never reject
250 | }, playTime );
251 |
252 | }, waitTime );
253 |
254 | } );
255 |
256 | return setPromise;
257 |
258 | };
259 |
260 | } )();
261 |
262 | this.tween = ( function() {
263 |
264 | var waitTime, playTime;
265 |
266 | var destRot = new THREE.Euler(); // input euler values
267 |
268 | var qa = new THREE.Quaternion(); // source quaternion
269 | var qb = new THREE.Quaternion(); // destination quaternion
270 | var qm = new THREE.Quaternion(); // worker
271 |
272 | return function( frame ) {
273 |
274 | var tweenPromise = new Promise( function( resolve, reject ) {
275 |
276 | tweenInProgress = true;
277 |
278 | waitTime = frame.offset * 1000;
279 | playTime = frame.duration * 1000;
280 |
281 | var _x = THREE.Math.degToRad( frame.data.beta || 0 );
282 | var _y = THREE.Math.degToRad( frame.data.alpha || 0 );
283 | var _z = THREE.Math.degToRad( frame.data.gamma || 0 );
284 |
285 | destRot.set( _x, _y, -_z, 'YXZ' );
286 |
287 | // Obtain target quaternion from provided Euler angles
288 | qb.setFromEuler( destRot );
289 | qb.multiply( worldQuat );
290 |
291 | // Set source as the current camera's quaternion
292 | qa.copy( controls.object.quaternion );
293 |
294 | // Reset our worker quaternion
295 | qm.set( 0, 0, 0, 1 );
296 |
297 | var throwError = window.setTimeout( function() {
298 | tweenInProgress = false;
299 | reject();
300 | }, waitTime + 5000 );
301 |
302 | var o = {
303 | t: 0
304 | };
305 |
306 | new TWEEN.Tween( o )
307 | .delay( waitTime )
308 | .to( {
309 | t: 1
310 | }, playTime )
311 | .onStart( function() {
312 | window.clearTimeout( throwError );
313 | } )
314 | .onUpdate( function() {
315 | THREE.Quaternion.slerp( qa, qb, qm, o.t );
316 | controls.object.quaternion.copy( qm );
317 | } )
318 | .onComplete( function() {
319 | tweenInProgress = false;
320 | resolve();
321 | } )
322 | .start();
323 |
324 | } );
325 |
326 | return tweenPromise;
327 |
328 | };
329 |
330 | } )();
331 |
332 | this.updateScreenOrientation = function( data ) {
333 |
334 | // Update the screen display bars
335 |
336 | var screenTop = scene.getObjectByProperty( 'name', 'screen_top', true );
337 | var screenBottom = scene.getObjectByProperty( 'name', 'screen_bottom', true );
338 | var screenTopInv = scene.getObjectByProperty( 'name', 'screen_top_inverse', true );
339 | var screenBottomInv = scene.getObjectByProperty( 'name', 'screen_bottom_inverse', true );
340 |
341 | var screenLeft = scene.getObjectByProperty( 'name', 'screen_left', true );
342 | var screenRight = scene.getObjectByProperty( 'name', 'screen_right', true );
343 | var screenLeftInv = scene.getObjectByProperty( 'name', 'screen_left_inverse', true );
344 | var screenRightInv = scene.getObjectByProperty( 'name', 'screen_right_inverse', true );
345 |
346 | if ( data.totalRotation % 180 !== 0 ) {
347 |
348 | screenTop.visible = false;
349 | screenBottom.visible = false;
350 | screenTopInv.visible = false;
351 | screenBottomInv.visible = false;
352 |
353 | if ( data.totalRotation == 90 ) {
354 |
355 | screenLeft.visible = true;
356 | screenRight.visible = true;
357 | screenLeftInv.visible = false;
358 | screenRightInv.visible = false;
359 |
360 | } else {
361 |
362 | screenLeft.visible = false;
363 | screenRight.visible = false;
364 | screenLeftInv.visible = true;
365 | screenRightInv.visible = true;
366 |
367 | }
368 |
369 | } else {
370 |
371 | screenLeft.visible = false;
372 | screenRight.visible = false;
373 | screenLeftInv.visible = false;
374 | screenRightInv.visible = false;
375 |
376 | if ( data.totalRotation == 180 ) {
377 |
378 | screenTop.visible = false;
379 | screenBottom.visible = false;
380 | screenTopInv.visible = true;
381 | screenBottomInv.visible = true;
382 |
383 | } else {
384 |
385 | screenTop.visible = true;
386 | screenBottom.visible = true;
387 | screenTopInv.visible = false;
388 | screenBottomInv.visible = false;
389 |
390 | }
391 |
392 | }
393 |
394 | if ( data.updateControls ) {
395 |
396 | controls.updateScreenOrientation( data.rotationDiff );
397 |
398 | }
399 |
400 | }
401 |
402 | var dispatch = function( array, event ) {
403 |
404 | for ( var i = 0, l = array.length; i < l; i++ ) {
405 |
406 | array[ i ]( event );
407 |
408 | }
409 |
410 | };
411 |
412 | var animate = function( time ) {
413 |
414 | request = requestAnimationFrame( animate );
415 |
416 | dispatch( events.update, {
417 | time: time,
418 | delta: time - prevTime
419 | } );
420 |
421 | if ( tweenInProgress ) {
422 | TWEEN.update( time );
423 | }
424 |
425 | controls.update();
426 |
427 | // *** Calculate device orientation quaternion (without affecting rendering)
428 | camQuat.copy( controls.object.quaternion );
429 | camQuat.inverse();
430 | camQuat.multiply( worldQuat );
431 | camQuat.inverse();
432 |
433 | // Derive Tait-Bryan angles from calculated device orientation quaternion
434 | deviceOrientation.setFromQuaternion( camQuat, 'YXZ' );
435 |
436 | // Calculate required emulator screen roll compensation required
437 | var rollZ = rotation.setFromQuaternion( controls.object.quaternion, 'YXZ' ).z;
438 | deviceOrientation.roll = THREE.Math.radToDeg( -rollZ );
439 |
440 | // Dispatch a new 'deviceorientation' event based on derived device orientation
441 | dispatchDeviceOrientationEvent( deviceOrientation );
442 |
443 | // Render the controller
444 | renderer.render( scene, camera );
445 |
446 | prevTime = time;
447 |
448 | };
449 |
450 | this.play = function( url ) {
451 |
452 | controls.object.quaternion.set( 0, 0, 0, 1 );
453 |
454 | request = requestAnimationFrame( animate );
455 | prevTime = performance.now();
456 |
457 | };
458 |
459 | this.stop = function() {
460 |
461 | cancelAnimationFrame( request );
462 |
463 | };
464 |
465 | }
466 |
467 | };
468 |
--------------------------------------------------------------------------------
/controller/js/third_party/fulltilt.min.js:
--------------------------------------------------------------------------------
1 | /*! Full Tilt v0.5.3 / http://github.com/richtr/Full-Tilt */
2 | !function(a){function b(a){return a=+a,0===a||isNaN(a)?a:a>0?1:-1}function c(a){var b=new Promise(function(b,c){var d=function(e){setTimeout(function(){a&&a.data?b():e>=20?c():d(++e)},50)};d(0)});return b}function d(){o=n?(a.screen.orientation.angle||0)*j:(a.orientation||0)*j}function e(a){l.orientation.data=a;for(var b in l.orientation.callbacks)l.orientation.callbacks[b].call(this)}function f(a){l.motion.data=a;for(var b in l.motion.callbacks)l.motion.callbacks[b].call(this)}if(void 0===a.FULLTILT||null===a.FULLTILT){var g=Math.PI,h=g/2,i=2*g,j=g/180,k=180/g,l={orientation:{active:!1,callbacks:[],data:void 0},motion:{active:!1,callbacks:[],data:void 0}},m=!1,n=a.screen&&a.screen.orientation&&void 0!==a.screen.orientation.angle&&null!==a.screen.orientation.angle?!0:!1,o=(n?a.screen.orientation.angle:a.orientation||0)*j,p=h,q=g,r=i/3,s=-h,t={};t.version="0.5.3",t.getDeviceOrientation=function(a){var b=new Promise(function(b,d){var e=new t.DeviceOrientation(a);e.start();var f=new c(l.orientation);f.then(function(){e._alphaAvailable=l.orientation.data.alpha&&null!==l.orientation.data.alpha,e._betaAvailable=l.orientation.data.beta&&null!==l.orientation.data.beta,e._gammaAvailable=l.orientation.data.gamma&&null!==l.orientation.data.gamma,b(e)})["catch"](function(){e.stop(),d("DeviceOrientation is not supported")})});return b},t.getDeviceMotion=function(a){var b=new Promise(function(b,d){var e=new t.DeviceMotion(a);e.start();var f=new c(l.motion);f.then(function(){e._accelerationXAvailable=l.motion.data.acceleration&&l.motion.data.acceleration.x,e._accelerationYAvailable=l.motion.data.acceleration&&l.motion.data.acceleration.y,e._accelerationZAvailable=l.motion.data.acceleration&&l.motion.data.acceleration.z,e._accelerationIncludingGravityXAvailable=l.motion.data.accelerationIncludingGravity&&l.motion.data.accelerationIncludingGravity.x,e._accelerationIncludingGravityYAvailable=l.motion.data.accelerationIncludingGravity&&l.motion.data.accelerationIncludingGravity.y,e._accelerationIncludingGravityZAvailable=l.motion.data.accelerationIncludingGravity&&l.motion.data.accelerationIncludingGravity.z,e._rotationRateAlphaAvailable=l.motion.data.rotationRate&&l.motion.data.rotationRate.alpha,e._rotationRateBetaAvailable=l.motion.data.rotationRate&&l.motion.data.rotationRate.beta,e._rotationRateGammaAvailable=l.motion.data.rotationRate&&l.motion.data.rotationRate.gamma,b(e)})["catch"](function(){e.stop(),d("DeviceMotion is not supported")})});return b},t.Quaternion=function(a,c,d,e){var f;this.set=function(a,b,c,d){this.x=a||0,this.y=b||0,this.z=c||0,this.w=d||1},this.copy=function(a){this.x=a.x,this.y=a.y,this.z=a.z,this.w=a.w},this.setFromEuler=function(){var a,b,c,d,e,f,g,h,i,k,l,m;return function(n){return n=n||{},c=(n.alpha||0)*j,a=(n.beta||0)*j,b=(n.gamma||0)*j,f=c/2,d=a/2,e=b/2,g=Math.cos(d),h=Math.cos(e),i=Math.cos(f),k=Math.sin(d),l=Math.sin(e),m=Math.sin(f),this.set(k*h*i-g*l*m,g*l*i+k*h*m,g*h*m+k*l*i,g*h*i-k*l*m),this.normalize(),this}}(),this.setFromRotationMatrix=function(){var a;return function(c){return a=c.elements,this.set(.5*Math.sqrt(1+a[0]-a[4]-a[8])*b(a[7]-a[5]),.5*Math.sqrt(1-a[0]+a[4]-a[8])*b(a[2]-a[6]),.5*Math.sqrt(1-a[0]-a[4]+a[8])*b(a[3]-a[1]),.5*Math.sqrt(1+a[0]+a[4]+a[8])),this}}(),this.multiply=function(a){return f=t.Quaternion.prototype.multiplyQuaternions(this,a),this.copy(f),this},this.rotateX=function(a){return f=t.Quaternion.prototype.rotateByAxisAngle(this,[1,0,0],a),this.copy(f),this},this.rotateY=function(a){return f=t.Quaternion.prototype.rotateByAxisAngle(this,[0,1,0],a),this.copy(f),this},this.rotateZ=function(a){return f=t.Quaternion.prototype.rotateByAxisAngle(this,[0,0,1],a),this.copy(f),this},this.normalize=function(){return t.Quaternion.prototype.normalize(this)},this.set(a,c,d,e)},t.Quaternion.prototype={constructor:t.Quaternion,multiplyQuaternions:function(){var a=new t.Quaternion;return function(b,c){var d=b.x,e=b.y,f=b.z,g=b.w,h=c.x,i=c.y,j=c.z,k=c.w;return a.set(d*k+g*h+e*j-f*i,e*k+g*i+f*h-d*j,f*k+g*j+d*i-e*h,g*k-d*h-e*i-f*j),a}}(),normalize:function(a){var b=Math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z+a.w*a.w);return 0===b?(a.x=0,a.y=0,a.z=0,a.w=1):(b=1/b,a.x*=b,a.y*=b,a.z*=b,a.w*=b),a},rotateByAxisAngle:function(){var a,b,c=new t.Quaternion,d=new t.Quaternion;return function(e,f,g){return a=(g||0)/2,b=Math.sin(a),d.set((f[0]||0)*b,(f[1]||0)*b,(f[2]||0)*b,Math.cos(a)),c=t.Quaternion.prototype.multiplyQuaternions(e,d),t.Quaternion.prototype.normalize(c)}}()},t.RotationMatrix=function(a,b,c,d,e,f,g,h,i){var k;this.elements=new Float32Array(9),this.identity=function(){return this.set(1,0,0,0,1,0,0,0,1),this},this.set=function(a,b,c,d,e,f,g,h,i){this.elements[0]=a||1,this.elements[1]=b||0,this.elements[2]=c||0,this.elements[3]=d||0,this.elements[4]=e||1,this.elements[5]=f||0,this.elements[6]=g||0,this.elements[7]=h||0,this.elements[8]=i||1},this.copy=function(a){this.elements[0]=a.elements[0],this.elements[1]=a.elements[1],this.elements[2]=a.elements[2],this.elements[3]=a.elements[3],this.elements[4]=a.elements[4],this.elements[5]=a.elements[5],this.elements[6]=a.elements[6],this.elements[7]=a.elements[7],this.elements[8]=a.elements[8]},this.setFromEuler=function(){var a,b,c,d,e,f,g,h,i;return function(k){return k=k||{},c=(k.alpha||0)*j,a=(k.beta||0)*j,b=(k.gamma||0)*j,d=Math.cos(a),e=Math.cos(b),f=Math.cos(c),g=Math.sin(a),h=Math.sin(b),i=Math.sin(c),this.set(f*e-i*g*h,-d*i,e*i*g+f*h,e*i+f*g*h,f*d,i*h-f*e*g,-d*h,g,d*e),this.normalize(),this}}(),this.setFromQuaternion=function(){var a,b,c,d;return function(e){return a=e.w*e.w,b=e.x*e.x,c=e.y*e.y,d=e.z*e.z,this.set(a+b-c-d,2*(e.x*e.y-e.w*e.z),2*(e.x*e.z+e.w*e.y),2*(e.x*e.y+e.w*e.z),a-b+c-d,2*(e.y*e.z-e.w*e.x),2*(e.x*e.z-e.w*e.y),2*(e.y*e.z+e.w*e.x),a-b-c+d),this}}(),this.multiply=function(a){return k=t.RotationMatrix.prototype.multiplyMatrices(this,a),this.copy(k),this},this.rotateX=function(a){return k=t.RotationMatrix.prototype.rotateByAxisAngle(this,[1,0,0],a),this.copy(k),this},this.rotateY=function(a){return k=t.RotationMatrix.prototype.rotateByAxisAngle(this,[0,1,0],a),this.copy(k),this},this.rotateZ=function(a){return k=t.RotationMatrix.prototype.rotateByAxisAngle(this,[0,0,1],a),this.copy(k),this},this.normalize=function(){return t.RotationMatrix.prototype.normalize(this)},this.set(a,b,c,d,e,f,g,h,i)},t.RotationMatrix.prototype={constructor:t.RotationMatrix,multiplyMatrices:function(){var a,b,c=new t.RotationMatrix;return function(d,e){return a=d.elements,b=e.elements,c.set(a[0]*b[0]+a[1]*b[3]+a[2]*b[6],a[0]*b[1]+a[1]*b[4]+a[2]*b[7],a[0]*b[2]+a[1]*b[5]+a[2]*b[8],a[3]*b[0]+a[4]*b[3]+a[5]*b[6],a[3]*b[1]+a[4]*b[4]+a[5]*b[7],a[3]*b[2]+a[4]*b[5]+a[5]*b[8],a[6]*b[0]+a[7]*b[3]+a[8]*b[6],a[6]*b[1]+a[7]*b[4]+a[8]*b[7],a[6]*b[2]+a[7]*b[5]+a[8]*b[8]),c}}(),normalize:function(a){var b=a.elements,c=b[0]*b[4]*b[8]-b[0]*b[5]*b[7]-b[1]*b[3]*b[8]+b[1]*b[5]*b[6]+b[2]*b[3]*b[7]-b[2]*b[4]*b[6];return b[0]/=c,b[1]/=c,b[2]/=c,b[3]/=c,b[4]/=c,b[5]/=c,b[6]/=c,b[7]/=c,b[8]/=c,a.elements=b,a},rotateByAxisAngle:function(){var a,b,c=new t.RotationMatrix,d=new t.RotationMatrix,e=!1;return function(f,g,h){return d.identity(),e=!1,a=Math.sin(h),b=Math.cos(h),1===g[0]&&0===g[1]&&0===g[2]?(e=!0,d.elements[4]=b,d.elements[5]=-a,d.elements[7]=a,d.elements[8]=b):1===g[1]&&0===g[0]&&0===g[2]?(e=!0,d.elements[0]=b,d.elements[2]=a,d.elements[6]=-a,d.elements[8]=b):1===g[2]&&0===g[0]&&0===g[1]&&(e=!0,d.elements[0]=b,d.elements[1]=-a,d.elements[3]=a,d.elements[4]=b),e?(c=t.RotationMatrix.prototype.multiplyMatrices(f,d),c=t.RotationMatrix.prototype.normalize(c)):c=f,c}}()},t.Euler=function(a,b,c){this.set=function(a,b,c){this.alpha=a||0,this.beta=b||0,this.gamma=c||0},this.copy=function(a){this.alpha=a.alpha,this.beta=a.beta,this.gamma=a.gamma},this.setFromRotationMatrix=function(){var a,b,c,d;return function(e){a=e.elements,a[8]>0?(b=Math.atan2(-a[1],a[4]),c=Math.asin(a[7]),d=Math.atan2(-a[6],a[8])):a[8]<0?(b=Math.atan2(a[1],-a[4]),c=-Math.asin(a[7]),c+=c>=0?-g:g,d=Math.atan2(a[6],-a[8])):a[6]>0?(b=Math.atan2(-a[1],a[4]),c=Math.asin(a[7]),d=-h):a[6]<0?(b=Math.atan2(a[1],-a[4]),c=-Math.asin(a[7]),c+=c>=0?-g:g,d=-h):(b=Math.atan2(a[3],a[0]),c=a[7]>0?h:-h,d=0),0>b&&(b+=i),b*=k,c*=k,d*=k,this.set(b,c,d)}}(),this.setFromQuaternion=function(){var a,b,c;return function(d){var e=d.w*d.w,f=d.x*d.x,j=d.y*d.y,l=d.z*d.z,m=e+f+j+l,n=d.w*d.x+d.y*d.z,o=1e-6;if(n>(.5-o)*m)a=2*Math.atan2(d.y,d.w),b=h,c=0;else if((-.5+o)*m>n)a=-2*Math.atan2(d.y,d.w),b=-h,c=0;else{var p=e-f+j-l,q=2*(d.w*d.z-d.x*d.y),r=e-f-j+l,s=2*(d.w*d.y-d.x*d.z);r>0?(a=Math.atan2(q,p),b=Math.asin(2*n/m),c=Math.atan2(s,r)):(a=Math.atan2(-q,-p),b=-Math.asin(2*n/m),b+=0>b?g:-g,c=Math.atan2(-s,-r))}0>a&&(a+=i),a*=k,b*=k,c*=k,this.set(a,b,c)}}(),this.rotateX=function(a){return t.Euler.prototype.rotateByAxisAngle(this,[1,0,0],a),this},this.rotateY=function(a){return t.Euler.prototype.rotateByAxisAngle(this,[0,1,0],a),this},this.rotateZ=function(a){return t.Euler.prototype.rotateByAxisAngle(this,[0,0,1],a),this},this.set(a,b,c)},t.Euler.prototype={constructor:t.Euler,rotateByAxisAngle:function(){var a=new t.RotationMatrix;return function(b,c,d){return a.setFromEuler(b),a=t.RotationMatrix.prototype.rotateByAxisAngle(a,c,d),b.setFromRotationMatrix(a),b}}()},t.DeviceOrientation=function(b){this.options=b||{};var c=0,d=200,e=0,f=10;if(this.alphaOffsetScreen=0,this.alphaOffsetDevice=void 0,"game"===this.options.type){var g=function(b){return null!==b.alpha&&(this.alphaOffsetDevice=new t.Euler(b.alpha,0,0),this.alphaOffsetDevice.rotateZ(-o),++e>=f)?void a.removeEventListener("deviceorientation",g,!1):void(++c>=d&&a.removeEventListener("deviceorientation",g,!1))}.bind(this);a.addEventListener("deviceorientation",g,!1)}else if("world"===this.options.type){var h=function(b){return b.absolute!==!0&&void 0!==b.webkitCompassAccuracy&&null!==b.webkitCompassAccuracy&&+b.webkitCompassAccuracy>=0&&+b.webkitCompassAccuracy<50&&(this.alphaOffsetDevice=new t.Euler(b.webkitCompassHeading,0,0),this.alphaOffsetDevice.rotateZ(o),this.alphaOffsetScreen=o,++e>=f)?void a.removeEventListener("deviceorientation",h,!1):void(++c>=d&&a.removeEventListener("deviceorientation",h,!1))}.bind(this);a.addEventListener("deviceorientation",h,!1)}},t.DeviceOrientation.prototype={constructor:t.DeviceOrientation,start:function(b){b&&"[object Function]"==Object.prototype.toString.call(b)&&l.orientation.callbacks.push(b),m||(n?a.screen.orientation.addEventListener("change",d,!1):a.addEventListener("orientationchange",d,!1)),l.orientation.active||(a.addEventListener("deviceorientation",e,!1),l.orientation.active=!0)},stop:function(){l.orientation.active&&(a.removeEventListener("deviceorientation",e,!1),l.orientation.active=!1)},listen:function(a){this.start(a)},getFixedFrameQuaternion:function(){var a=new t.Euler,b=new t.RotationMatrix,c=new t.Quaternion;return function(){var d=l.orientation.data||{alpha:0,beta:0,gamma:0},e=d.alpha;return this.alphaOffsetDevice&&(b.setFromEuler(this.alphaOffsetDevice),b.rotateZ(-this.alphaOffsetScreen),a.setFromRotationMatrix(b),a.alpha<0&&(a.alpha+=360),a.alpha%=360,e-=a.alpha),a.set(e,d.beta,d.gamma),c.setFromEuler(a),c}}(),getScreenAdjustedQuaternion:function(){var a;return function(){return a=this.getFixedFrameQuaternion(),a.rotateZ(-o),a}}(),getFixedFrameMatrix:function(){var a=new t.Euler,b=new t.RotationMatrix;return function(){var c=l.orientation.data||{alpha:0,beta:0,gamma:0},d=c.alpha;return this.alphaOffsetDevice&&(b.setFromEuler(this.alphaOffsetDevice),b.rotateZ(-this.alphaOffsetScreen),a.setFromRotationMatrix(b),a.alpha<0&&(a.alpha+=360),a.alpha%=360,d-=a.alpha),a.set(d,c.beta,c.gamma),b.setFromEuler(a),b}}(),getScreenAdjustedMatrix:function(){var a;return function(){return a=this.getFixedFrameMatrix(),a.rotateZ(-o),a}}(),getFixedFrameEuler:function(){var a,b=new t.Euler;return function(){return a=this.getFixedFrameMatrix(),b.setFromRotationMatrix(a),b}}(),getScreenAdjustedEuler:function(){var a,b=new t.Euler;return function(){return a=this.getScreenAdjustedMatrix(),b.setFromRotationMatrix(a),b}}(),isAbsolute:function(){return l.orientation.data&&l.orientation.data.absolute===!0?!0:!1},getLastRawEventData:function(){return l.orientation.data||{}},_alphaAvailable:!1,_betaAvailable:!1,_gammaAvailable:!1,isAvailable:function(a){switch(a){case this.ALPHA:return this._alphaAvailable;case this.BETA:return this._betaAvailable;case this.GAMMA:return this._gammaAvailable}},ALPHA:"alpha",BETA:"beta",GAMMA:"gamma"},t.DeviceMotion=function(a){this.options=a||{}},t.DeviceMotion.prototype={constructor:t.DeviceMotion,start:function(b){b&&"[object Function]"==Object.prototype.toString.call(b)&&l.motion.callbacks.push(b),m||(n?a.screen.orientation.addEventListener("change",d,!1):a.addEventListener("orientationchange",d,!1)),l.motion.active||(a.addEventListener("devicemotion",f,!1),l.motion.active=!0)},stop:function(){l.motion.active&&(a.removeEventListener("devicemotion",f,!1),l.motion.active=!1)},listen:function(a){this.start(a)},getScreenAdjustedAcceleration:function(){var a=l.motion.data&&l.motion.data.acceleration?l.motion.data.acceleration:{x:0,y:0,z:0},b={};switch(o){case p:b.x=-a.y,b.y=a.x;break;case q:b.x=-a.x,b.y=-a.y;break;case r:case s:b.x=a.y,b.y=-a.x;break;default:b.x=a.x,b.y=a.y}return b.z=a.z,b},getScreenAdjustedAccelerationIncludingGravity:function(){var a=l.motion.data&&l.motion.data.accelerationIncludingGravity?l.motion.data.accelerationIncludingGravity:{x:0,y:0,z:0},b={};switch(o){case p:b.x=-a.y,b.y=a.x;break;case q:b.x=-a.x,b.y=-a.y;break;case r:case s:b.x=a.y,b.y=-a.x;break;default:b.x=a.x,b.y=a.y}return b.z=a.z,b},getScreenAdjustedRotationRate:function(){var a=l.motion.data&&l.motion.data.rotationRate?l.motion.data.rotationRate:{alpha:0,beta:0,gamma:0},b={};switch(o){case p:b.beta=-a.gamma,b.gamma=a.beta;break;case q:b.beta=-a.beta,b.gamma=-a.gamma;break;case r:case s:b.beta=a.gamma,b.gamma=-a.beta;break;default:b.beta=a.beta,b.gamma=a.gamma}return b.alpha=a.alpha,b},getLastRawEventData:function(){return l.motion.data||{}},_accelerationXAvailable:!1,_accelerationYAvailable:!1,_accelerationZAvailable:!1,_accelerationIncludingGravityXAvailable:!1,_accelerationIncludingGravityYAvailable:!1,_accelerationIncludingGravityZAvailable:!1,_rotationRateAlphaAvailable:!1,_rotationRateBetaAvailable:!1,_rotationRateGammaAvailable:!1,isAvailable:function(a){switch(a){case this.ACCELERATION_X:return this._accelerationXAvailable;case this.ACCELERATION_Y:return this._accelerationYAvailable;case this.ACCELERATION_Z:return this._accelerationZAvailable;case this.ACCELERATION_INCLUDING_GRAVITY_X:return this._accelerationIncludingGravityXAvailable;case this.ACCELERATION_INCLUDING_GRAVITY_Y:return this._accelerationIncludingGravityYAvailable;case this.ACCELERATION_INCLUDING_GRAVITY_Z:return this._accelerationIncludingGravityZAvailable;case this.ROTATION_RATE_ALPHA:return this._rotationRateAlphaAvailable;case this.ROTATION_RATE_BETA:return this._rotationRateBetaAvailable;case this.ROTATION_RATE_GAMMA:return this._rotationRateGammaAvailable}},ACCELERATION_X:"accelerationX",ACCELERATION_Y:"accelerationY",ACCELERATION_Z:"accelerationZ",ACCELERATION_INCLUDING_GRAVITY_X:"accelerationIncludingGravityX",ACCELERATION_INCLUDING_GRAVITY_Y:"accelerationIncludingGravityY",ACCELERATION_INCLUDING_GRAVITY_Z:"accelerationIncludingGravityZ",ROTATION_RATE_ALPHA:"rotationRateAlpha",ROTATION_RATE_BETA:"rotationRateBeta",ROTATION_RATE_GAMMA:"rotationRateGamma"},a.FULLTILT=t}}(window);
3 |
--------------------------------------------------------------------------------
/emulator/js/libs/jquery-1.10.2.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
2 | //@ sourceMappingURL=jquery-1.10.2.min.map
3 | */
4 | (function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" | t |