Merge branch 'master' into tizen_2.1
[platform/framework/web/web-ui-fw.git] / src / js / widgets / jquery.mobile.tizen.gallery3d.js
1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: 3D photo gallery widget.
3 //>>label: Gallery3d
4 //>>group: Tizen:Widgets
5
6 define( [ "components/imageloader", "components/motionpath", "components/webgl" ], function ( ) {
7 //>>excludeEnd("jqmBuildExclude");
8
9
10 /* ***************************************************************************
11         Flora License
12
13         Version 1.0, April, 2013
14
15         http://floralicense.org/license/
16
17         TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
18
19         1. Definitions.
20
21         "License" shall mean the terms and conditions for use, reproduction,
22         and distribution as defined by Sections 1 through 9 of this document.
23
24         "Licensor" shall mean the copyright owner or entity authorized by
25         the copyright owner that is granting the License.
26
27         "Legal Entity" shall mean the union of the acting entity and
28         all other entities that control, are controlled by, or are
29         under common control with that entity. For the purposes of
30         this definition, "control" means (i) the power, direct or indirect,
31         to cause the direction or management of such entity,
32         whether by contract or otherwise, or (ii) ownership of fifty percent (50%)
33         or more of the outstanding shares, or (iii) beneficial ownership of
34         such entity.
35
36         "You" (or "Your") shall mean an individual or Legal Entity
37         exercising permissions granted by this License.
38
39         "Source" form shall mean the preferred form for making modifications,
40         including but not limited to software source code, documentation source,
41         and configuration files.
42
43         "Object" form shall mean any form resulting from mechanical
44         transformation or translation of a Source form, including but
45         not limited to compiled object code, generated documentation,
46         and conversions to other media types.
47
48         "Work" shall mean the work of authorship, whether in Source or Object form,
49         made available under the License, as indicated by a copyright notice
50         that is included in or attached to the work (an example is provided
51         in the Appendix below).
52
53         "Derivative Works" shall mean any work, whether in Source or Object form,
54         that is based on (or derived from) the Work and for which the editorial
55         revisions, annotations, elaborations, or other modifications represent,
56         as a whole, an original work of authorship. For the purposes of this License,
57         Derivative Works shall not include works that remain separable from,
58         or merely link (or bind by name) to the interfaces of, the Work and
59         Derivative Works thereof.
60
61         "Contribution" shall mean any work of authorship, including the original
62         version of the Work and any modifications or additions to that Work or
63         Derivative Works thereof, that is intentionally submitted to Licensor
64         for inclusion in the Work by the copyright owner or by an individual or
65         Legal Entity authorized to submit on behalf of the copyright owner.
66         For the purposes of this definition, "submitted" means any form of
67         electronic, verbal, or written communication sent to the Licensor or
68         its representatives, including but not limited to communication on
69         electronic mailing lists, source code control systems, and issue
70         tracking systems that are managed by, or on behalf of, the Licensor
71         for the purpose of discussing and improving the Work, but excluding
72         communication that is conspicuously marked or otherwise designated
73         in writing by the copyright owner as "Not a Contribution."
74
75         "Contributor" shall mean Licensor and any individual or Legal Entity
76         on behalf of whom a Contribution has been received by Licensor and
77         subsequently incorporated within the Work.
78
79         "Tizen Certified Platform" shall mean a software platform that complies
80         with the standards set forth in the Tizen Compliance Specification
81         and passes the Tizen Compliance Tests as defined from time to time
82         by the Tizen Technical Steering Group and certified by the Tizen
83         Association or its designated agent.
84
85         2. Grant of Copyright License.  Subject to the terms and conditions of
86         this License, each Contributor hereby grants to You a perpetual,
87         worldwide, non-exclusive, no-charge, royalty-free, irrevocable
88         copyright license to reproduce, prepare Derivative Works of,
89         publicly display, publicly perform, sublicense, and distribute the
90         Work and such Derivative Works in Source or Object form.
91
92         3. Grant of Patent License.  Subject to the terms and conditions of
93         this License, each Contributor hereby grants to You a perpetual,
94         worldwide, non-exclusive, no-charge, royalty-free, irrevocable
95         (except as stated in this section) patent license to make, have made,
96         use, offer to sell, sell, import, and otherwise transfer the Work
97         solely as incorporated into a Tizen Certified Platform, where such
98         license applies only to those patent claims licensable by such
99         Contributor that are necessarily infringed by their Contribution(s)
100         alone or by combination of their Contribution(s) with the Work solely
101         as incorporated into a Tizen Certified Platform to which such
102         Contribution(s) was submitted. If You institute patent litigation
103         against any entity (including a cross-claim or counterclaim
104         in a lawsuit) alleging that the Work or a Contribution incorporated
105         within the Work constitutes direct or contributory patent infringement,
106         then any patent licenses granted to You under this License for that
107         Work shall terminate as of the date such litigation is filed.
108
109         4. Redistribution.  You may reproduce and distribute copies of the
110         Work or Derivative Works thereof pursuant to the copyright license
111         above, in any medium, with or without modifications, and in Source or
112         Object form, provided that You meet the following conditions:
113
114           1. You must give any other recipients of the Work or Derivative Works
115                  a copy of this License; and
116           2. You must cause any modified files to carry prominent notices stating
117                  that You changed the files; and
118           3. You must retain, in the Source form of any Derivative Works that
119                  You distribute, all copyright, patent, trademark, and attribution
120                  notices from the Source form of the Work, excluding those notices
121                  that do not pertain to any part of the Derivative Works; and
122           4. If the Work includes a "NOTICE" text file as part of its distribution,
123                  then any Derivative Works that You distribute must include a readable
124                  copy of the attribution notices contained within such NOTICE file,
125                  excluding those notices that do not pertain to any part of
126                  the Derivative Works, in at least one of the following places:
127                  within a NOTICE text file distributed as part of the Derivative Works;
128                  within the Source form or documentation, if provided along with the
129                  Derivative Works; or, within a display generated by the Derivative Works,
130                  if and wherever such third-party notices normally appear.
131                  The contents of the NOTICE file are for informational purposes only
132                  and do not modify the License.
133
134         You may add Your own attribution notices within Derivative Works
135         that You distribute, alongside or as an addendum to the NOTICE text
136         from the Work, provided that such additional attribution notices
137         cannot be construed as modifying the License. You may add Your own
138         copyright statement to Your modifications and may provide additional or
139         different license terms and conditions for use, reproduction, or
140         distribution of Your modifications, or for any such Derivative Works
141         as a whole, provided Your use, reproduction, and distribution of
142         the Work otherwise complies with the conditions stated in this License.
143
144         5. Submission of Contributions. Unless You explicitly state otherwise,
145         any Contribution intentionally submitted for inclusion in the Work
146         by You to the Licensor shall be under the terms and conditions of
147         this License, without any additional terms or conditions.
148         Notwithstanding the above, nothing herein shall supersede or modify
149         the terms of any separate license agreement you may have executed
150         with Licensor regarding such Contributions.
151
152         6. Trademarks.  This License does not grant permission to use the trade
153         names, trademarks, service marks, or product names of the Licensor,
154         except as required for reasonable and customary use in describing the
155         origin of the Work and reproducing the content of the NOTICE file.
156
157         7. Disclaimer of Warranty. Unless required by applicable law or
158         agreed to in writing, Licensor provides the Work (and each
159         Contributor provides its Contributions) on an "AS IS" BASIS,
160         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
161         implied, including, without limitation, any warranties or conditions
162         of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
163         PARTICULAR PURPOSE. You are solely responsible for determining the
164         appropriateness of using or redistributing the Work and assume any
165         risks associated with Your exercise of permissions under this License.
166
167         8. Limitation of Liability. In no event and under no legal theory,
168         whether in tort (including negligence), contract, or otherwise,
169         unless required by applicable law (such as deliberate and grossly
170         negligent acts) or agreed to in writing, shall any Contributor be
171         liable to You for damages, including any direct, indirect, special,
172         incidental, or consequential damages of any character arising as a
173         result of this License or out of the use or inability to use the
174         Work (including but not limited to damages for loss of goodwill,
175         work stoppage, computer failure or malfunction, or any and all
176         other commercial damages or losses), even if such Contributor
177         has been advised of the possibility of such damages.
178
179         9. Accepting Warranty or Additional Liability. While redistributing
180         the Work or Derivative Works thereof, You may choose to offer,
181         and charge a fee for, acceptance of support, warranty, indemnity,
182         or other liability obligations and/or rights consistent with this
183         License. However, in accepting such obligations, You may act only
184         on Your own behalf and on Your sole responsibility, not on behalf
185         of any other Contributor, and only if You agree to indemnify,
186         defend, and hold each Contributor harmless for any liability
187         incurred by, or claims asserted against, such Contributor by reason
188         of your accepting any such warranty or additional liability.
189
190         END OF TERMS AND CONDITIONS
191
192         APPENDIX: How to apply the Flora License to your work
193
194         To apply the Flora License to your work, attach the following
195         boilerplate notice, with the fields enclosed by brackets "[]"
196         replaced with your own identifying information. (Don't include
197         the brackets!) The text should be enclosed in the appropriate
198         comment syntax for the file format. We also recommend that a
199         file or class name and description of purpose be included on the
200         same "printed page" as the copyright notice for easier
201         identification within third-party archives.
202
203            Copyright [yyyy] [name of copyright owner]
204
205            Licensed under the Flora License, Version 1.0 (the "License");
206            you may not use this file except in compliance with the License.
207            You may obtain a copy of the License at
208
209                    http://floralicense.org/license/
210
211            Unless required by applicable law or agreed to in writing, software
212            distributed under the License is distributed on an "AS IS" BASIS,
213            WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
214            See the License for the specific language governing permissions and
215            limitations under the License.
216
217
218  * Authors: Hyunsook Park <hyunsook.park@samsung.com>
219  *                      Wonseop Kim <wonseop.kim@samsung.com>
220  */
221
222 /**
223  *      The gallery3d widget displays images along a curved path on a 3-dimensional coordinate system.
224  *      To improve performance, the size of image(s) displayed on the screen should be a square(under
225  *      128X128 pixel) as possible. But if a user can't resize the images, this widget supports an image
226  *      resizing feature and he/she can use it with "data-thumbnail-cache" option. ("data-thumbnail-cache"
227  *      option resizes the gallery images under 128x128 pixels and stores the images on a local storage.
228  *      So when a gallery3D widget is re-launched, the widget reuse the storage and a user can improve
229  *      launching time. A browser or web runtime engine should support "Web Storage" feature to use that
230  *      option.)
231  *
232  *      HTML Attributes:
233  *
234  *              data-thumbnail-cache : Determines whether to cache and resize images.
235  *
236  *      APIs:
237  *
238  *              next ( void )
239  *                      : This method moves each image forward one by one.
240  *              prev ( void )
241  *                      : This method moves each image backward one by one.
242  *              select ( [number] )
243  *                      : When the "select" method is called with an argument, the method selects the image of given index.
244  *                      If the method is called with no argument, it will return the Javascript object having "src"
245  *                      attribute having the selected image's URL.
246  *              add ( object or string [, number] )
247  *                      This method adds an image to Gallery3D widget.
248  *                      If the second argument isn't inputted, the image is added at the 0th position.
249  *              remove ( [number] )
250  *                      : This method deletes an image from Gallery3d widget.
251  *                      The argument defines the index of the image to be deleted.
252  *                      If an argument isn't inputted, it removes current image.
253  *              clearThumbnailCache ( void )
254  *                      : This method clears the cache data of all images when thumbnailCache option is set as 'true'.
255  *              refresh ( void )
256  *                      : This method updates and redraws current widget.
257  *              empty ( void )
258  *                      : This method removes all of images from Gallery3D widget.
259  *              length ( void )
260  *                      : This method gets the number of images.
261  *
262  *      Events:
263  *
264  *              select : Triggered when an image is selected.
265  *
266  *      Examples:
267  *
268  *              <script>
269  *                      $( "#gallery3d" ).on( "gallery3dcreate", function () {
270  *                              $( "#gallery3d" ).gallery3d( "add", "01.jpg" );
271  *                      });
272  *              </script>
273  *              <div id="gallery3d" data-role="gallery3d"></div>
274  */
275
276 /**
277         @class Gallery3D
278         The gallery3d widget displays images along a curved path on a 3-dimensional coordinate system.
279         <br/><br/>To add an gallery3d widget to the application, use the following code:
280
281                 <script>
282                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
283                                 $( "#gallery3d" ).gallery3d( "add", "01.jpg" );
284                         });
285                 </script>
286                 <div id="gallery3d" data-role="gallery3d"></div>
287 */
288 /**
289         @property {Boolean} data-thumbnail-cache
290         Determines whether to cache and resize images.
291         To improve performance, the size of image(s) displayed on the screen should be a square (under 128X128 pixels).
292         "data-thumbnail-cache" option resizes the gallery images under 128x128 pixels and stores the images on a local storage.
293         So when a gallery3D widget is re-launched, the widget reuses the storage and the launching time can be improved.
294         A browser or web runtime engine must support "Web Storage" feature to use this option.
295 */
296 /**
297         @event select
298         Triggered when an image is selected.
299
300                 <script>
301                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
302                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
303                                         .gallery3d( "add", { src: "2.jpg" } )
304                                         .gallery3d( "add", { src: "3.jpg" } );
305                         }).on( "select", function ( event, data, index ) {
306                                 // Handle the select event
307                                 var urlOfImage = data.src, indexOfImage = index;
308                         });
309                 </script>
310                 <div id="gallery3d" data-role="gallery3d"></div>
311 */
312 /**
313         @method next
314         This method moves each image forward one by one.
315
316                 <script>
317                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
318                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
319                                         .gallery3d( "add", { src: "2.jpg" } )
320                                         .gallery3d( "add", { src: "3.jpg" } )
321                                         .gallery3d( "next" );
322                         });
323                 </script>
324                 <div id="gallery3d" data-role="gallery3d"></div>
325 */
326 /**
327         @method prev
328         This method moves each image backward one by one.
329
330                 <script>
331                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
332                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
333                                         .gallery3d( "add", { src: "2.jpg" } )
334                                         .gallery3d( "add", { src: "3.jpg" } )
335                                         .gallery3d( "prev" );
336                         });
337                 </script>
338                 <div id="gallery3d" data-role="gallery3d"></div>
339 */
340 /**
341         @method select
342         When the "select" method is called with an argument, the method selects the image of given index.
343         If the method is called with no argument, it will return the Javascript object having "src" attribute having the selected image's URL.
344
345                 <script>
346                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
347                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
348                                         .gallery3d( "add", { src: "2.jpg" } )
349                                         .gallery3d( "add", { src: "3.jpg" } );
350                                 var selectedImage = $("#gallery3d"). gallery3d( "select" );
351                                 // selectedImage = { src: "3.jpg" };
352                         });
353                 </script>
354                 <div id="gallery3d" data-role="gallery3d"></div>
355 */
356 /**
357         @method add
358         This method adds an image to Gallery3D widget.
359         The first argument is a Javascript object having a "src" attribute or a string of image's path.
360         The second argument is an index of images.
361         If second argument isn't inputted, the image is added at the 0th position.
362
363                 <script>
364                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
365                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
366                                         .gallery3d( "add", "2.jpg", 1 );
367                         });
368                 </script>
369                 <div id="gallery3d" data-role="gallery3d"></div>
370 */
371 /**
372         @method remove
373         This method deletes an image from Gallery3d widget.
374         The argument defines the index of the image to be deleted.
375         If an argument isn't inputted, it removes current image.
376
377                 <script>
378                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
379                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
380                                         .gallery3d( "add", { src: "2.jpg" } )
381                                         .gallery3d( "add", { src: "3.jpg" } );
382
383                                 $( "#gallery3d" ).gallery3d( "remove" );
384                                 $( "#gallery3d" ).gallery3d( "remove", 1 );
385                         });
386                 </script>
387                 <div id="gallery3d" data-role="gallery3d"></div>
388 */
389 /**
390         @method clearThumbnailCache
391         This method clears the cache data of all images when thumbnailCache option is set as 'true'
392
393                 <script>
394                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
395                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
396                                         .gallery3d( "add", { src: "2.jpg" } )
397                                         .gallery3d( "add", { src: "3.jpg" } );
398
399                                 $( "#gallery3d" ).gallery3d( "clearThumbnailCache" );
400                         });
401                 </script>
402                 <div id="gallery3d" data-role="gallery3d" data-thumbnail-cache="true"></div>
403 */
404 /**
405         @method refresh
406         This method updates and redraws current widget.
407
408                 <script>
409                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
410                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
411                                         .gallery3d( "add", { src: "2.jpg" } )
412                                         .gallery3d( "add", { src: "3.jpg" } );
413
414                                 $( "#gallery3d" ).gallery3d( "refresh" );
415                         });
416                 </script>
417                 <div id="gallery3d" data-role="gallery3d"></div>
418 */
419 /**
420         @method empty
421         This method removes all of images from Gallery3D widget.
422
423                 <script>
424                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
425                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
426                                         .gallery3d( "add", { src: "2.jpg" } )
427                                         .gallery3d( "add", { src: "3.jpg" } );
428
429                                 $( "#gallery3d" ).gallery3d( "empty" );
430                         });
431                 </script>
432                 <div id="gallery3d" data-role="gallery3d"></div>
433 */
434 /**
435         @method length
436         This method gets the number of images.
437
438                 <script>
439                         $( "#gallery3d" ).on( "gallery3dcreate", function () {
440                                 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
441                                         .gallery3d( "add", { src: "2.jpg" } )
442                                         .gallery3d( "add", { src: "3.jpg" } );
443
444                                 var imagesLength = $( "#gallery3d" ).gallery3d( "length" );
445                                 // imagesLength = 3;
446                         });
447                 </script>
448                 <div id="gallery3d" data-role="gallery3d"></div>
449 */
450
451 ( function ( $, document, window, undefined ) {
452         function Node() {
453                 this.vertices = [
454                         -1.0, -1.0, 0.0,
455                         1.0, -1.0, 0.0,
456                         1.0,  1.0, 0.0,
457                         -1.0,  1.0, 0.0
458                 ];
459                 this.textureCoords = [
460                         1.0, 0.0,
461                         0.0, 0.0,
462                         0.0, 1.0,
463                         1.0, 1.0
464                 ];
465                 this.normalVectors = [
466                         0.0, 0.0, 1.0,
467                         0.0, 0.0, 1.0,
468                         0.0, 0.0, 1.0,
469                         0.0, 0.0, 1.0
470                 ];
471                 this.texture = null;
472                 this.textureBuffer = null;
473                 this.textureBufferItemSize = 0;
474                 this.mashOrder = [];
475                 this.mvMatrix = null;
476                 this.level = -1;
477                 this.targetLevel = 0;
478                 this.drawable = false;
479                 this.image = null;
480                 this.imageID = 0;
481         }
482
483         var isPreInitailization  = false,
484                 glMatrix = {},
485                 VERTEX_SHADER,
486                 FRAGMENT_SHADER,
487                 GlArray32,
488                 GlArray16,
489                 preInitialize = function () {
490                         if ( isPreInitailization ) {
491                                 return;
492                         }
493
494                         window.initGlMatrix( glMatrix );
495
496                         VERTEX_SHADER = [
497                                 "attribute vec3 aVertexPosition;",
498                                 "attribute vec2 aTextureCoord;",
499                                 "attribute vec3 aVertexNormal;",
500                                 "uniform mat4 uMoveMatrix;",
501                                 "uniform mat4 uPerspectiveMatrix;",
502                                 "uniform mat3 nNormalMatrix;",
503                                 "uniform vec3 uAmbientColor;",
504                                 "uniform vec3 uLightDirection;",
505                                 "uniform vec3 uDirectionColor;",
506                                 "uniform vec3 uLightDirection_first;",
507                                 "uniform vec3 uLightDirection_second;",
508                                 "varying vec2 vTextureCoord;",
509                                 "varying vec3 vLightWeight;",
510                                 "varying vec4 vFogWeight;",
511
512                                 "void main(void) {",
513                                 "       vec4 v_Position = uMoveMatrix * vec4(aVertexPosition, 1.0);",
514                                 "       gl_Position = uPerspectiveMatrix * v_Position;",
515                                 "       vTextureCoord = aTextureCoord;",
516                                 "       float fog = 1.0 - ((gl_Position.z + 1.5) / 60.0);",
517                                 "       vFogWeight = clamp( vec4( fog, fog, fog, 1.0), 0.6, 1.0);",
518                                 "       vec3 transNormalVector = nNormalMatrix * aVertexNormal;",
519
520                                 "       float vLightWeightFirst = 0.0;",
521                                 "       float vLightWeightSecond = max( dot(transNormalVector, uLightDirection_second), 0.0 );",
522
523                                 "       vLightWeight = uAmbientColor + uDirectionColor * vLightWeightSecond;",
524                                 "}"
525                         ].join( "\n" );
526
527                         FRAGMENT_SHADER = [
528                                 "precision mediump float;",
529                                 "varying vec2 vTextureCoord;",
530                                 "varying vec3 vLightWeight;",
531                                 "uniform sampler2D uSampler;",
532                                 "varying vec4 vFogWeight;",
533
534                                 "void main(void) {",
535                                 "       vec4 TextureColor;",
536                                 "       if ( vTextureCoord.s <= 0.01 || vTextureCoord.s >= 0.99 || vTextureCoord.t <= 0.01 || vTextureCoord.t >= 0.99 ) {",
537                                 "               TextureColor = vec4(1.0, 1.0, 1.0, 0.5);",
538                                 "       } else {",
539                                 "               TextureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));",
540                                 "       }",
541                                 "       TextureColor *= vFogWeight;",
542                                 "       gl_FragColor = vec4(TextureColor.rgb * vLightWeight, TextureColor.a);",
543                                 "}"
544                         ].join( "\n" );
545
546                         GlArray32 = ( typeof window.Float32Array !== "undefined" ?
547                                         window.Float32Array :
548                                                 ( typeof window.WebGLFloatArray !== "undefined" ? window.WebGLFloatArray : Array ) );
549
550                         GlArray16 = ( typeof window.Uint16Array !== "undefined" ? window.Uint16Array : Array );
551
552                         isPreInitailization = true;
553                 },
554                 degreeToRadian = function ( degree ) {
555                         return degree * Math.PI / 180;
556                 },
557                 getContext3D = function ( canvas ) {
558                         var gl, i,
559                                 contextNames = [ "experimental-webgl", "webkit-3d", "webgl", "moz-webgl" ];
560
561                         for ( i = 0; i < contextNames.length; i += 1 ) {
562                                 try {
563                                         gl = canvas.getContext( contextNames[i] );
564                                         if ( gl ) {
565                                                 break;
566                                         }
567                                 } catch ( e ) {
568                                         $( canvas ).html( "Unfortunately, there's a WebGL compatibility problem. </br> You may want to check your system settings." );
569                                         return;
570                                 }
571                         }
572                         return gl;
573                 },
574                 requestAnimationFrame = function ( callback ) {
575                         var id = window.setTimeout( callback, 1000 / 60 );
576                         return id;
577                 },
578                 cancelAnimationFrame = function ( id ) {
579                         window.clearTimeout( id );
580                 };
581
582         $.widget( "tizen.gallery3d", $.mobile.widget, {
583                 options: {
584                         thumbnailCache: false
585                 },
586
587                 _MAX_ITEM_COUNT: 28,
588                 _ANIMATION_END: 999,
589                 _DURATION_DEFAULT: 300,
590                 _DURATION_FIRST: 1600,
591                 _VIEWPORT_WIDTH: 1024,
592                 _VIEWPORT_HEIGHT: 456,
593                 _DIRECTION_LEFT: -1,
594                 _DIRECTION_RIGHT: +1,
595
596                 _gl: null,
597                 _shaderProgram : null,
598                 _positionBuffer : null,
599                 _textureCoordBuffer : null,
600                 _normalVectorBuffer : null,
601                 _nodes: null,
602                 _pMatrix : null,
603                 _animationID: 0,
604                 _dragInterval : 0,
605                 _startTime : 0,
606                 _sumTime : 0,
607                 _lightsPositionStack : [
608                         [0.0, 0.0, -1.0],       // back
609                         [-0.2, 0.0, 0.7]        // front
610                 ],
611                 _path: null,
612                 _swipeThresholdOfBasetimeGap: ( $.support.touch ? 30 : 70 ),
613                 _swipeThresholdOfSensitivity: ( $.support.touch ? 2.0 : 10.0 ),
614                 _canvas: null,
615                 _imageList: [],
616                 _maxDrawLength: 0,
617                 _firstImageNumber: 0,
618                 _lastImageNumber: 0,
619
620                 _create: function () {
621                         var self = this,
622                                 view = self.element,
623                                 option = self.options;
624
625                         preInitialize();
626
627                         self._canvas = $( "<canvas class='ui-gallery3d-canvas'></canvas>" );
628
629                         view.addClass( "ui-gallery3d" ).append( self._canvas );
630                         self._addBehavier();
631
632                         self._dragInterval = 1000 / 30; // 30fps
633
634                         $.each( self.options, function ( key, value ) {
635                                 self.options[ key ] = undefined;
636                                 self._setOption( key, value );
637                         });
638                 },
639
640                 destroy: function () {
641                         this._final();
642                         $.mobile.widget.prototype.destroy.call( this );
643                 },
644
645                 _setOption: function ( key, value ) {
646                         switch ( key ) {
647                         case "thumbnailCache" :
648                                 if ( typeof value === "string" ) {
649                                         value = ( value === "true" ) ? true : false;
650                                 } else {
651                                         value = !!value;
652                                 }
653                                 this._reset();
654                                 break;
655                         }
656
657                         $.mobile.widget.prototype._setOption.call( this, key, value );
658                 },
659
660                 _init: function ( canvas ) {
661                         var self = this,
662                                 pathPoints = [
663                                         [40, 0, -48],
664                                         [-12, 0, -40],  // contorl Point of Point1
665                                         [24, 0, -9],            // contorl Point of Point2
666                                         [-5, 0, -5]
667                                 ],
668                                 i;
669
670                         canvas = canvas || self._canvas;
671
672                         if ( !canvas ) {
673                                 return;
674                         }
675
676                         self._gl = self._gl || self._initGL( canvas[0] );
677                         if ( !self._gl ) {
678                                 return;
679                         }
680
681                         if ( !self._imageList ) {
682                                 return;
683                         }
684
685                         self._shaderProgram = self._shaderProgram || self._initShader( self._gl );
686                         if ( !self._shaderProgram ) {
687                                 return;
688                         }
689
690                         if ( self._imageList.length > self._MAX_ITEM_COUNT ) {
691                                 self._firstImageNumber = self._imageList.length - 1;
692                                 self._lastImageNumber = self._MAX_ITEM_COUNT - 1;
693                         }
694
695                         self._nodes = self._initBuffers( self._gl, self._shaderProgram );
696
697                         self._initTextures( self._gl, self._nodes );
698
699                         self._path = $.motionpath( "bezier2d", {
700                                 points: pathPoints,
701                                 maxLevel: self._MAX_ITEM_COUNT
702                         } );
703                         for ( i = 0; i < self._nodes.length; i += 1 ) {
704                                 self._path.levels[i] = self._path.levels[i + 1] || 0;
705                                 self._nodes[i].level = i;
706                         }
707                 },
708
709                 _final: function ( canvas ) {
710                         var self = this,
711                                 gl = self._gl;
712
713                         if ( !gl ) {
714                                 return;
715                         }
716
717                         canvas = canvas || self._canvas;
718
719                         $( self._nodes ).each( function ( i ) {
720                                 var node = self._nodes[i];
721                                 gl.deleteTexture( node.texture );
722                                 node.texture = null;
723                         });
724                         self._nodes = null;
725
726                         gl.deleteBuffer( self._positionBuffer );
727                         self._positionBuffer = null;
728                         gl.deleteBuffer( self._textureCoordBuffer );
729                         self._textureCoordBuffer = null;
730                         gl.deleteBuffer( self._normalVectorBuffer );
731                         self._normalVectorBuffer = null;
732
733                         $.webgl.shader.deleteShaders( gl );
734                         gl.deleteProgram( self._shaderProgram );
735                         self._shaderProgram = null;
736
737                         self._gl = gl = null;
738                 },
739
740                 _addBehavier : function () {
741                         var self = this,
742                                 view = self.element,
743                                 canvas = self._canvas,
744                                 touchStartEvt = ( $.support.touch ? "touchstart" : "mousedown" ),
745                                 touchMoveEvt = ( $.support.touch ? "touchmove" : "mousemove" ) + ".gallery3d",
746                                 touchEndEvt = ( $.support.touch ? "touchend" : "mouseup" ) + ".gallery3d",
747                                 touchLeaveEvt = ( $.support.touch ? "touchleave" : "mouseout" ) + ".gallery3d";
748
749                         canvas.on( "webglcontextlost", function ( e ) {
750                                 e.preventDefault();
751                         }).on( "webglcontextrestored", function ( e ) {
752                                 self._init();
753                         }).on( touchStartEvt, function ( e ) {
754                                 var i = 0,
755                                         startX = 0,
756                                         deltaMaxSteps = 20,
757                                         deltas = [ deltaMaxSteps ],
758                                         deltaTimes = [ deltaMaxSteps ],
759                                         deltaIndex = 0,
760                                         dragValue = 0,
761                                         dragDirection = false,
762                                         prevTime = 0;
763
764                                 e.preventDefault();
765                                 e.stopPropagation();
766
767                                 if ( self._imageList.length <= 1 ) {
768                                         return;
769                                 }
770
771                                 self._stop();
772
773                                 startX =  $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
774                                 prevTime = $.now();
775
776                                 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
777                                         deltas[i] = startX;
778                                         deltaTimes[i] = $.now();
779                                 }
780
781                                 deltaIndex += 1;
782
783                                 view.on( touchMoveEvt, function ( e ) {
784                                         var x, dx, interval;
785
786                                         e.preventDefault();
787                                         e.stopPropagation();
788
789                                         x =  $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
790                                         dx = startX - x;
791
792                                         deltas[deltaIndex] = x;
793                                         deltaTimes[deltaIndex] = $.now();
794                                         interval = deltaTimes[deltaIndex] - prevTime;
795
796                                         deltaIndex = ( deltaIndex + 1 ) % deltaMaxSteps;
797
798                                         // Validation of drag
799                                         if ( Math.abs( dx ) >= 10 && interval >= self._dragInterval ) {
800                                                 if ( dragDirection !== ( ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT ) ) {
801                                                         dragValue = 0;
802                                                         dragDirection = ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT;
803                                                 }
804
805                                                 dragValue += Math.abs( dx ) / 100;
806                                                 if ( dragValue >= 1 ) {
807                                                         self._setPosition( self._ANIMATION_END, dragDirection );
808                                                         dragValue = 0;
809                                                 } else {
810                                                         self._setPosition( dragValue, dragDirection );
811                                                 }
812                                                 self._drawScene();
813                                                 startX = x;
814                                                 prevTime = $.now();
815                                         }
816                                 }).on( touchEndEvt, function ( e ) {
817                                         var baseTime = 0,
818                                                 recent = -1,
819                                                 index = 0,
820                                                 previous = 0,
821                                                 baseTimeRatio = 0,
822                                                 fx = 0,
823                                                 lastX = 0,
824                                                 velocityX = 0,
825                                                 dx = 0,
826                                                 isSwipe = true,
827                                                 direction;
828
829                                         e.preventDefault();
830                                         e.stopPropagation();
831
832                                         // Validation of swipe
833                                         baseTime = $.now() - self._swipeThresholdOfBasetimeGap;
834                                         lastX = $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
835                                         dx = startX - lastX;
836                                         startX = 0;
837                                         for ( i = 0; i < deltaMaxSteps; i += 1 ) {
838                                                 index = ( deltaIndex + i ) % deltaMaxSteps;
839                                                 if ( deltaTimes[index] > baseTime ) {
840                                                         recent = index;
841                                                         break;
842                                                 }
843                                         }
844                                         if ( recent < 0 ) {
845                                                 isSwipe = false;
846                                         }
847
848                                         if ( isSwipe ) {
849                                                 previous = recent;
850                                                 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
851                                                         previous = ( previous - 1 + deltaMaxSteps ) % deltaMaxSteps;
852                                                         if ( deltaTimes[previous] < deltaTimes[recent] ) {
853                                                                 break;
854                                                         }
855                                                 }
856                                                 // too slow or too fast
857                                                 if ( i === deltaMaxSteps || baseTime < deltaTimes[previous] ) {
858                                                         isSwipe = false;
859                                                 }
860                                         }
861
862                                         if ( isSwipe ) {
863                                                 baseTimeRatio = ( baseTime - deltaTimes[previous] ) / ( deltaTimes[recent] - deltaTimes[previous] );
864                                                 fx = ( 1.0 - baseTimeRatio ) * deltas[previous] + baseTimeRatio * deltas[recent];
865                                                 if ( Math.abs( fx - lastX ) < self._swipeThresholdOfSensitivity ) {
866                                                         fx = lastX;
867                                                 }
868                                                 velocityX = parseInt( ( lastX - fx ) / ( $.now() - baseTime ), 10 );
869                                         }
870
871                                         if ( isSwipe && velocityX ) {
872                                                 direction = ( velocityX < 0 ) ? self._DIRECTION_LEFT : self._DIRECTION_RIGHT;
873                                                 self._run( direction, Math.abs( velocityX ), dragValue );
874                                         } else if ( dragDirection !== 0 && dragValue ) {
875                                                 self._animate( null, self._DURATION_DEFAULT * ( 1 - dragValue ), dragDirection, 0, dragValue );
876                                         }
877
878                                         view.unbind( ".gallery3d" );
879                                 }).on( touchLeaveEvt, function ( e ) {
880                                         view.trigger( touchEndEvt );
881                                 });
882                         });
883                 },
884
885                 // ----------------------------------------------------------
886                 // WebGL
887                 // ----------------------------------------------------------
888                 _initGL: function ( canvas ) {
889                         var self = this,
890                                 mat4 = glMatrix.mat4,
891                                 gl;
892
893                         gl = getContext3D( canvas );
894                         if ( !gl ) {
895                                 return null;
896                         }
897
898                         gl.enable( gl.BLEND );
899                         gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
900
901                         gl.enable( gl.DEPTH_TEST );
902                         gl.depthFunc( gl.LEQUAL );
903
904                         canvas.width = self._VIEWPORT_WIDTH;
905                         canvas.height = self._VIEWPORT_HEIGHT;
906                         gl.viewportWidth = canvas.width;
907                         gl.viewportHeight = canvas.height;
908                         gl.viewport( 0, 0, gl.viewportWidth, gl.viewportHeight );
909                         self._pMatrix = mat4.create();
910                         mat4.perspective( 40, gl.viewportWidth / gl.viewportHeight, 0.1, 10000.0, self._pMatrix );
911
912                         gl.clearColor( 0.15, 0.15, 0.15, 1.0 );
913                         gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
914
915                         return gl;
916                 },
917
918                 _initShader : function ( gl ) {
919                         var self = this,
920                                 shaderProgram;
921
922                         shaderProgram = $.webgl.shader.addShaderProgram( self._gl, VERTEX_SHADER, FRAGMENT_SHADER );
923                         gl.useProgram( shaderProgram );
924
925                         shaderProgram.vertexPositionAttr = gl.getAttribLocation( shaderProgram, "aVertexPosition" );
926                         gl.enableVertexAttribArray( shaderProgram.vertexPositionAttr );
927
928                         shaderProgram.textureCoordAttr = gl.getAttribLocation( shaderProgram, "aTextureCoord" );
929                         gl.enableVertexAttribArray( shaderProgram.textureCoordAttr );
930
931                         // Set light normal vectors for lighting~
932                         shaderProgram.vertexNormalAttr = gl.getAttribLocation( shaderProgram, "aVertexNormal" );
933                         gl.enableVertexAttribArray( shaderProgram.vertexNormalAttr );
934
935                         shaderProgram.perspectiveMU = gl.getUniformLocation( shaderProgram, "uPerspectiveMatrix");
936                         shaderProgram.transformMU = gl.getUniformLocation( shaderProgram, "uMoveMatrix");
937                         shaderProgram.sampleUniform = gl.getUniformLocation( shaderProgram, "uSampler");
938
939                         // Set light variables~
940                         shaderProgram.normalMU = gl.getUniformLocation( shaderProgram, "nNormalMatrix");
941                         shaderProgram.ambientColorU = gl.getUniformLocation( shaderProgram, "uAmbientColor");
942                         shaderProgram.lightDirU_first = gl.getUniformLocation( shaderProgram, "uLightDirection_first");
943                         shaderProgram.lightDirU_second = gl.getUniformLocation( shaderProgram, "uLightDirection_second");
944                         shaderProgram.directionColorU = gl.getUniformLocation( shaderProgram, "uDirectionColor");
945
946                         return shaderProgram;
947                 },
948
949                 _initBuffers: function ( gl, shaderProgram ) {
950                         var self = this,
951                                 i = 0,
952                                 mashBase = 0,
953                                 vertices = [],
954                                 textureCoords = [],
955                                 normalVectors = [],
956                                 nodes = [],
957                                 maxDrawLength = self._MAX_ITEM_COUNT;
958
959                         for ( i = 0; i < self._imageList.length + 1; i += 1 ) {
960                                 nodes[i] = new Node();
961                                 $.merge( vertices, nodes[i].vertices );
962                                 $.merge( textureCoords, nodes[i].textureCoords );
963                                 $.merge( normalVectors, nodes[i].normalVectors );
964
965                                 nodes[i].textureBuffer = gl.createBuffer();
966                                 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, nodes[i].textureBuffer );
967                                 mashBase = i * 4;
968                                 nodes[i].meshOrder = [
969                                         mashBase, mashBase + 1, mashBase + 2,
970                                         mashBase + 2, mashBase + 3, mashBase
971                                 ];
972                                 gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new GlArray16( nodes[i].meshOrder ), gl.STATIC_DRAW );
973                                 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null ); // release buffer memory
974                                 nodes[i].textureBufferItemSize = 6;
975                         }
976
977                         self._positionBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( vertices ) );
978                         self._positionBuffer.itemSize = 3;
979
980                         self._textureCoordBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( textureCoords ) );
981                         self._textureCoordBuffer.itemSize = 2;
982
983                         self._normalVectorBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( normalVectors ) ); // Vertex's normal vector for Direction light
984                         self._normalVectorBuffer.itemSize = 3;
985
986                         // Ambient light
987                         gl.uniform3f( shaderProgram.ambientColorU, 0.1, 0.1, 0.1 );
988                         // Direcntion light
989                         gl.uniform3f( shaderProgram.directionColorU, 1.0, 1.0, 1.0 );
990
991                         return nodes;
992                 },
993
994                 // ----------------------------------------------------------
995                 // Texture
996                 // ----------------------------------------------------------
997                 _initTextures: function ( gl, nodes ) {
998                         var self = this;
999
1000                         $( nodes ).each( function ( i ) {
1001                                 var node = nodes[i],
1002                                         url;
1003
1004                                 if ( !self._imageList[i] ) {
1005                                         return false;
1006                                 }
1007
1008                                 url = self._imageList[i].src;
1009                                 node.texture = gl.createTexture();
1010                                 self._loadImage( url, i, i, gl, nodes );
1011                         });
1012                 },
1013
1014                 _loadImage: function ( url, i, imageID, gl, nodes ) {
1015                         var self = this,
1016                                 isMipmap = false,
1017                                 image,
1018                                 node;
1019
1020                         gl = gl || self._gl;
1021                         nodes = nodes || self._nodes;
1022                         isMipmap = isMipmap || false;
1023                         node = nodes[i];
1024                         node.image = node.image || new Image();
1025
1026                         $( node.image ).one( "load", function ( e ) {
1027                                 self._bindTexture( gl, node, this, isMipmap );
1028                                 node.imageID = imageID;
1029
1030                                 if ( !self._animationID ) {
1031                                         self._setPosition( 0, 0 );
1032                                 }
1033                         });
1034
1035                         if ( self.options.thumbnailCache ) {
1036                                 $.imageloader.getThumbnail( url, function ( result ) {
1037                                         if ( result === "NOT_FOUND_ERR" ) {
1038                                                 $.imageloader.setThumbnail( url, function ( result ) {
1039                                                         if ( result && result.length > 30 ) {
1040                                                                 node.image.src = result;
1041                                                                 isMipmap = true;
1042                                                         } else {
1043                                                                 node.image.src = url;
1044                                                         }
1045                                                 });
1046                                         } else if ( result && result.length > 30 ) {
1047                                                 node.image.src = result;
1048                                                 isMipmap = true;
1049                                         } else {
1050                                                 node.image.src = url;
1051                                         }
1052                                 });
1053                         } else {
1054                                 node.image.src = url;
1055                         }
1056                 },
1057
1058                 _bindTexture: function ( gl, node, image, isMipmap ) {
1059                         if ( !node || !node.texture ) {
1060                                 return;
1061                         }
1062
1063                         gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
1064
1065                         gl.bindTexture( gl.TEXTURE_2D, node.texture );
1066                         gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
1067
1068                         if ( isMipmap ) {
1069                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
1070                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST );
1071                                 gl.generateMipmap( gl.TEXTURE_2D );
1072                         } else {
1073                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
1074                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
1075                         }
1076
1077                         gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
1078                         gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
1079
1080                         node.texture.loaded = true;
1081
1082                         // release texture memory
1083                         gl.bindTexture( gl.TEXTURE_2D, null );
1084                 },
1085
1086                 // ----------------------------------------------------------
1087                 // rendering
1088                 // ----------------------------------------------------------
1089                 _setPosition: function ( progress, direction ) {
1090                         var self = this,
1091                                 mat4 = glMatrix.mat4,
1092                                 nodes = self._nodes,
1093                                 imageList = self._imageList,
1094                                 imageListLength = imageList.length,
1095                                 itemCount = self._MAX_ITEM_COUNT,
1096                                 displayLength = ( imageListLength > itemCount ) ? itemCount : imageListLength,
1097                                 nextLevelLenth = 0,
1098                                 i = 0,
1099                                 t = 0,
1100                                 position = 0,
1101                                 angle = 0,
1102                                 current = 0,
1103                                 next = 0,
1104                                 nextLevel = 0,
1105                                 path = self._path,
1106                                 nextImageID = 0;
1107
1108                         nextLevelLenth = ( direction >= 0 ) ? displayLength + 1 : displayLength;
1109
1110                         if ( !nodes[i].level ) {
1111                                 nodes[i].level = displayLength;
1112                         }
1113
1114                         for ( i = 0; i < displayLength; i += 1 ) {
1115                                 if ( !nodes[i].mvMatrix ) {
1116                                         nodes[i].mvMatrix = mat4.create();
1117                                 }
1118
1119                                 if ( direction > 0 && nodes[i].level >= displayLength ) {
1120                                         nodes[i].level = 0;
1121                                 }
1122
1123                                 current = path.levels[nodes[i].level];
1124                                 nextLevel = ( nodes[i].level + nextLevelLenth + direction ) % nextLevelLenth;
1125                                 next = path.levels[nextLevel];
1126
1127                                 if ( imageListLength > itemCount ) {
1128                                         if ( direction > 0 && nextLevel === 1
1129                                                         && self._firstImageNumber !== nodes[i].imageID ) {
1130                                                 self._loadImage( imageList[self._firstImageNumber].src, i, self._firstImageNumber );
1131                                         } else if ( direction < 0 && nextLevel === nextLevelLenth - 1
1132                                                         && self._lastImageNumber !== nodes[i].imageID ) {
1133                                                 self._loadImage( imageList[self._lastImageNumber].src, i, self._lastImageNumber );
1134                                         }
1135                                 }
1136
1137                                 mat4.identity( nodes[i].mvMatrix );
1138                                 mat4.translate( nodes[i].mvMatrix, [-2.0, -2.0, 1.0] );
1139                                 mat4.rotate( nodes[i].mvMatrix, degreeToRadian( 19 ), [1, 0, 0] );
1140
1141                                 t = ( current + ( next - current ) * ( ( progress > 1 ) ? 1 : progress ) );
1142
1143                                 if ( progress >= self._ANIMATION_END ) {
1144                                         nodes[i].level = nextLevel || displayLength;
1145                                         t = path.levels[nodes[i].level];
1146                                 }
1147
1148                                 if ( ( progress < self._ANIMATION_END )
1149                                                 && ( direction <= 0 && nodes[i].level < 1 ) ) {
1150                                         nodes[i].drawable = false;
1151                                 } else {
1152                                         nodes[i].drawable = true;
1153                                 }
1154
1155                                 if ( progress === self._ANIMATION_END && nodes[i].level === 1 ) {
1156                                         self.element.trigger( "select", imageList[ nodes[i].imageID ], nodes[i].imageID );
1157                                 }
1158
1159                                 position = path.getPosition( t );
1160                                 angle = path.getAngle( t );
1161
1162                                 mat4.translate( nodes[i].mvMatrix, position );
1163                                 mat4.rotate( nodes[i].mvMatrix, angle, [0, 1, 0] );
1164                         }
1165
1166                         if ( imageListLength > itemCount && progress >= self._ANIMATION_END ) {
1167                                 self._firstImageNumber = ( self._firstImageNumber - direction ) % imageListLength;
1168                                 if ( self._firstImageNumber < 0 ) {
1169                                         self._firstImageNumber = imageListLength - 1;
1170                                 }
1171
1172                                 self._lastImageNumber = ( self._lastImageNumber - direction ) % imageListLength;
1173                                 if ( self._lastImageNumber < 0 ) {
1174                                         self._lastImageNumber = imageListLength - 1;
1175                                 }
1176                         }
1177                         self._drawScene();
1178                 },
1179
1180                 _drawScene: function () {
1181                         if ( !this._gl || !this._shaderProgram ) {
1182                                 return;
1183                         }
1184
1185                         var self = this,
1186                                 gl = self._gl,
1187                                 shaderProgram = self._shaderProgram,
1188                                 nodes = self._nodes,
1189                                 nodesLength = nodes.length,
1190                                 i;
1191
1192                         gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
1193
1194                         gl.bindBuffer( gl.ARRAY_BUFFER, self._positionBuffer );
1195                         gl.vertexAttribPointer( shaderProgram.vertexPositionAttr, self._positionBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1196
1197                         gl.bindBuffer( gl.ARRAY_BUFFER, self._textureCoordBuffer );
1198                         gl.vertexAttribPointer( shaderProgram.textureCoordAttr, self._textureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1199
1200                         gl.bindBuffer( gl.ARRAY_BUFFER, self._normalVectorBuffer );
1201                         gl.vertexAttribPointer( shaderProgram.vertexNormalAttr, self._normalVectorBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1202
1203                         for ( i = 0; i < nodesLength; i += 1 ) {
1204                                 if ( nodes[i].drawable ) {
1205                                         self._drawElement( self._pMatrix, nodes[i] );
1206                                 }
1207                         }
1208                 },
1209
1210                 _drawElement: function ( perspectiveMatrix, targetNode ) {
1211                         var self = this,
1212                                 gl = self._gl,
1213                                 vec3 = glMatrix.vec3,
1214                                 mat3 = glMatrix.mat3,
1215                                 mat4 = glMatrix.mat4,
1216                                 shaderProgram = self._shaderProgram,
1217                                 moveMatrix = targetNode.mvMatrix,
1218                                 texture = targetNode.texture,
1219                                 meshIndexBuffer = targetNode.textureBuffer,
1220                                 meshIndexBufferItemSize = targetNode.textureBufferItemSize,
1221                                 lightPositions = self._lightsPositionStack,
1222                                 LightDir,
1223                                 normalMatrix;
1224
1225                         if ( !moveMatrix ) {
1226                                 return;
1227                         }
1228
1229                         gl.activeTexture( gl.TEXTURE0 );
1230                         if ( texture && texture.loaded ) {
1231                                 gl.bindTexture( gl.TEXTURE_2D, texture );
1232                         }
1233                         gl.uniform1i( shaderProgram.sampleUniform, 0 );
1234
1235                         LightDir = vec3.create();
1236                         vec3.normalize( lightPositions[0], LightDir );
1237                         vec3.scale( LightDir, -8 );
1238                         gl.uniform3fv( shaderProgram.lightDirU_first, LightDir );
1239
1240                         vec3.normalize( lightPositions[1], LightDir );
1241                         vec3.scale( LightDir, -1 );
1242                         gl.uniform3fv( shaderProgram.lightDirU_second, LightDir );
1243                         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, meshIndexBuffer );
1244
1245                         gl.uniformMatrix4fv( shaderProgram.perspectiveMU, false, perspectiveMatrix );
1246                         gl.uniformMatrix4fv( shaderProgram.transformMU, false, moveMatrix );
1247
1248                         normalMatrix = mat3.create();
1249                         mat4.toInverseMat3( moveMatrix, normalMatrix );
1250                         mat3.transpose( normalMatrix );
1251                         gl.uniformMatrix3fv( shaderProgram.normalMU, false, normalMatrix );
1252
1253                         gl.drawElements( gl.TRIANGLES, meshIndexBufferItemSize, gl.UNSIGNED_SHORT, 0 );
1254
1255                         // release buffer memory
1256                         gl.bindBuffer( gl.ARRAY_BUFFER, null );
1257                         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
1258
1259                         // release texture memory
1260                         gl.bindTexture( gl.TEXTURE_2D, null );
1261                 },
1262
1263                 // ----------------------------------------------------------
1264                 // Animation
1265                 // ----------------------------------------------------------
1266                 _animate: function ( easingType, duration, direction, repeatCount, startValue, _removeCount ) {
1267                         var self = this,
1268                                 timeNow = $.now(),
1269                                 progress,
1270                                 removeCount = 0;
1271
1272                         easingType = easingType || "linear";
1273                         startValue = startValue || 0;
1274                         _removeCount = _removeCount || 0;
1275
1276                         if ( self._sumTime >= duration ) {
1277                                 self._setPosition( self._ANIMATION_END, direction );
1278                                 self._stop();
1279                                 return;
1280                         }
1281
1282                         if ( self._startTime === 0 ) {
1283                                 self._startTime = timeNow;
1284                         } else {
1285                                 self._sumTime = timeNow - self._startTime;
1286                                 progress = $.easing[ easingType ]( self._sumTime / duration, self._sumTime, startValue, repeatCount + 1, duration );
1287                                 removeCount = parseInt( Math.abs( progress ), 10 );
1288
1289                                 if ( _removeCount !== removeCount ) {
1290                                         self._setPosition( self._ANIMATION_END, direction );
1291                                         _removeCount = removeCount;
1292
1293                                         if ( ( repeatCount - _removeCount ) >= 0 ) {
1294                                                 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
1295                                         } else {
1296                                                 self._stop();
1297                                         }
1298                                         return;
1299                                 }
1300
1301                                 self._setPosition( progress - _removeCount, direction );
1302                         }
1303
1304                         self._animationID = requestAnimationFrame( function () {
1305                                 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
1306                         });
1307                 },
1308
1309                 _run: function ( direction, repeatCount, startValue ) {
1310                         var self = this,
1311                                 repeat = repeatCount || 0,
1312                                 duration = self._DURATION_DEFAULT * ( repeat + 1 );
1313
1314                         if ( self._imageList.length <= 1 ) {
1315                                 return;
1316                         }
1317
1318                         startValue = startValue || 0;
1319                         duration = ( duration >= 0 ) ? duration : 0;
1320
1321                         if ( self._animationID ) {
1322                                 self._setPosition( self._ANIMATION_END, direction );
1323                                 self._stop();
1324                         }
1325
1326                         self._animate( "easeOutExpo", duration, direction, repeat, startValue );
1327                 },
1328
1329                 _reset: function () {
1330                         if ( !this._canvas || !this._gl ) {
1331                                 return;
1332                         }
1333
1334                         this._final();
1335                         this._init();
1336                         this.refresh();
1337                 },
1338
1339                 _stop: function () {
1340                         if ( this._animationID ) {
1341                                 cancelAnimationFrame( this._animationID );
1342                         }
1343                         this._animationID = 0;
1344
1345                         this._startTime = 0;
1346                         this._sumTime = 0;
1347                 },
1348
1349                 next: function () {
1350                         this._run( this._DIRECTION_LEFT , 0 );
1351                 },
1352
1353                 prev: function () {
1354                         this._run( this._DIRECTION_RIGHT, 0 );
1355                 },
1356
1357                 refresh: function () {
1358                         var view = this.element,
1359                                 canvas = view.find( "canvas.ui-gallery3d-canvas" );
1360
1361                         if ( canvas.width() !== view.width() ) {
1362                                 canvas.width( view.width() );
1363                         }
1364
1365                         if ( !this._animationID ) {
1366                                 this._setPosition( 0, 0 );
1367                         }
1368                 },
1369
1370                 select: function ( index ) {
1371                         var nodes = this._nodes,
1372                                 repeat,
1373                                 i,
1374                                 imageID,
1375                                 object = null,
1376                                 target = 0,
1377                                 direction = 0;
1378
1379                         if ( index && this._animationID ) {
1380                                 this._stop();
1381                         }
1382
1383                         for ( i in nodes ) {
1384                                 if ( nodes[i].level === 1 ) {
1385                                         object = this._imageList[ nodes[i].imageID ];
1386                                         imageID = nodes[i].imageID;
1387                                         break;
1388                                 }
1389                         }
1390
1391                         if ( !index ) {
1392                                 return object;
1393                         }
1394
1395                         if ( index < 0 && index >= this._imageList.length ) {
1396                                 return;
1397                         }
1398
1399                         target = index - imageID;
1400                         direction = ( target > 0 ) ? this._DIRECTION_LEFT
1401                                 : ( ( target < 0 ) ? this._DIRECTION_RIGHT : 0 );
1402                         if ( direction ) {
1403                                 this._run( direction, Math.abs( target ) - 1  );
1404                         }
1405                 },
1406
1407                 add: function ( item, index ) {
1408                         if ( !item ) {
1409                                 return;
1410                         }
1411
1412                         if ( typeof item === "string" ) {
1413                                 item = { "src" : item };
1414                         }
1415
1416                         index = index || 0;
1417                         if ( typeof index !== "number" && index < 0
1418                                         && index >= this._imageList.length ) {
1419                                 return;
1420                         }
1421
1422                         this._imageList.splice( index, 0, item );
1423                         if ( this._gl ) {
1424                                 this._reset();
1425                         }
1426                 },
1427
1428                 remove: function ( index ) {
1429                         index = index || 0;
1430                         if ( typeof index !== "number" && index < 0
1431                                         && index >= this._imageList.length ) {
1432                                 return;
1433                         }
1434
1435                         this._imageList.splice( index, 1 );
1436                         if ( this._gl ) {
1437                                 this._reset();
1438                         }
1439                 },
1440
1441                 clearThumbnailCache: function () {
1442                         if ( !this._nodes || ( this._nodes.length <= 0 ) ) {
1443                                 return;
1444                         }
1445
1446                         var i, url;
1447                         for ( i = 0; i < this._imageList.length; i += 1 ) {
1448                                 url = this._imageList[i].src;
1449                                 $.imageloader.removeThumbnail( url );
1450                         }
1451                 },
1452
1453                 empty: function () {
1454                         this._imageList = [];
1455                         this._reset();
1456                 },
1457
1458                 length: function () {
1459                         return this._imageList.length;
1460                 }
1461         });
1462
1463         $( document ).on( "pagecreate create", function ( e ) {
1464                 $( ":jqmData(role='gallery3d')" ).gallery3d();
1465         }).on( "pagechange", function ( e ) {
1466                 $( e.target ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
1467         });
1468
1469         $( window ).on( "resize orientationchange", function ( e ) {
1470                 $( ".ui-page-active" ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
1471         });
1472
1473 } ( jQuery, document, window ) );
1474
1475 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
1476 } );
1477 //>>excludeEnd("jqmBuildExclude");