License: Change flora license version
[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.1, 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.1 (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                         self._stop();
718
719                         canvas = canvas || self._canvas;
720
721                         $( self._nodes ).each( function ( i ) {
722                                 var node = self._nodes[i];
723                                 gl.deleteTexture( node.texture );
724                                 node.texture = null;
725                         });
726                         self._nodes = null;
727
728                         gl.deleteBuffer( self._positionBuffer );
729                         self._positionBuffer = null;
730                         gl.deleteBuffer( self._textureCoordBuffer );
731                         self._textureCoordBuffer = null;
732                         gl.deleteBuffer( self._normalVectorBuffer );
733                         self._normalVectorBuffer = null;
734
735                         $.webgl.shader.deleteShaders( gl );
736                         gl.deleteProgram( self._shaderProgram );
737                         self._shaderProgram = null;
738
739                         self._gl = gl = null;
740                 },
741
742                 _addBehavier : function () {
743                         var self = this,
744                                 view = self.element,
745                                 canvas = self._canvas,
746                                 touchStartEvt = ( $.support.touch ? "touchstart" : "mousedown" ),
747                                 touchMoveEvt = ( $.support.touch ? "touchmove" : "mousemove" ) + ".gallery3d",
748                                 touchEndEvt = ( $.support.touch ? "touchend" : "mouseup" ) + ".gallery3d",
749                                 touchLeaveEvt = ( $.support.touch ? "touchleave" : "mouseout" ) + ".gallery3d";
750
751                         canvas.on( "webglcontextlost", function ( e ) {
752                                 e.preventDefault();
753                         }).on( "webglcontextrestored", function ( e ) {
754                                 self._init();
755                         }).on( touchStartEvt, function ( e ) {
756                                 var i = 0,
757                                         startX = 0,
758                                         deltaMaxSteps = 20,
759                                         deltas = [ deltaMaxSteps ],
760                                         deltaTimes = [ deltaMaxSteps ],
761                                         deltaIndex = 0,
762                                         dragValue = 0,
763                                         dragDirection = false,
764                                         prevTime = 0;
765
766                                 e.preventDefault();
767                                 e.stopPropagation();
768
769                                 if ( self._imageList.length <= 1 ) {
770                                         return;
771                                 }
772
773                                 self._stop();
774
775                                 startX =  $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
776                                 prevTime = $.now();
777
778                                 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
779                                         deltas[i] = startX;
780                                         deltaTimes[i] = $.now();
781                                 }
782
783                                 deltaIndex += 1;
784
785                                 view.on( touchMoveEvt, function ( e ) {
786                                         var x, dx, interval;
787
788                                         e.preventDefault();
789                                         e.stopPropagation();
790
791                                         x =  $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
792                                         dx = startX - x;
793
794                                         deltas[deltaIndex] = x;
795                                         deltaTimes[deltaIndex] = $.now();
796                                         interval = deltaTimes[deltaIndex] - prevTime;
797
798                                         deltaIndex = ( deltaIndex + 1 ) % deltaMaxSteps;
799
800                                         // Validation of drag
801                                         if ( Math.abs( dx ) >= 10 && interval >= self._dragInterval ) {
802                                                 if ( dragDirection !== ( ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT ) ) {
803                                                         dragValue = 0;
804                                                         dragDirection = ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT;
805                                                 }
806
807                                                 dragValue += Math.abs( dx ) / 100;
808                                                 if ( dragValue >= 1 ) {
809                                                         self._setPosition( self._ANIMATION_END, dragDirection );
810                                                         dragValue = 0;
811                                                 } else {
812                                                         self._setPosition( dragValue, dragDirection );
813                                                 }
814                                                 self._drawScene();
815                                                 startX = x;
816                                                 prevTime = $.now();
817                                         }
818                                 }).on( touchEndEvt, function ( e ) {
819                                         var baseTime = 0,
820                                                 recent = -1,
821                                                 index = 0,
822                                                 previous = 0,
823                                                 baseTimeRatio = 0,
824                                                 fx = 0,
825                                                 lastX = 0,
826                                                 velocityX = 0,
827                                                 dx = 0,
828                                                 isSwipe = true,
829                                                 direction;
830
831                                         e.preventDefault();
832                                         e.stopPropagation();
833
834                                         // Validation of swipe
835                                         baseTime = $.now() - self._swipeThresholdOfBasetimeGap;
836                                         lastX = $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
837                                         dx = startX - lastX;
838                                         startX = 0;
839                                         for ( i = 0; i < deltaMaxSteps; i += 1 ) {
840                                                 index = ( deltaIndex + i ) % deltaMaxSteps;
841                                                 if ( deltaTimes[index] > baseTime ) {
842                                                         recent = index;
843                                                         break;
844                                                 }
845                                         }
846                                         if ( recent < 0 ) {
847                                                 isSwipe = false;
848                                         }
849
850                                         if ( isSwipe ) {
851                                                 previous = recent;
852                                                 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
853                                                         previous = ( previous - 1 + deltaMaxSteps ) % deltaMaxSteps;
854                                                         if ( deltaTimes[previous] < deltaTimes[recent] ) {
855                                                                 break;
856                                                         }
857                                                 }
858                                                 // too slow or too fast
859                                                 if ( i === deltaMaxSteps || baseTime < deltaTimes[previous] ) {
860                                                         isSwipe = false;
861                                                 }
862                                         }
863
864                                         if ( isSwipe ) {
865                                                 baseTimeRatio = ( baseTime - deltaTimes[previous] ) / ( deltaTimes[recent] - deltaTimes[previous] );
866                                                 fx = ( 1.0 - baseTimeRatio ) * deltas[previous] + baseTimeRatio * deltas[recent];
867                                                 if ( Math.abs( fx - lastX ) < self._swipeThresholdOfSensitivity ) {
868                                                         fx = lastX;
869                                                 }
870                                                 velocityX = parseInt( ( lastX - fx ) / ( $.now() - baseTime ), 10 );
871                                         }
872
873                                         if ( isSwipe && velocityX ) {
874                                                 direction = ( velocityX < 0 ) ? self._DIRECTION_LEFT : self._DIRECTION_RIGHT;
875                                                 self._run( direction, Math.abs( velocityX ), dragValue );
876                                         } else if ( dragDirection !== 0 && dragValue ) {
877                                                 self._animate( null, self._DURATION_DEFAULT * ( 1 - dragValue ), dragDirection, 0, dragValue );
878                                         }
879
880                                         view.unbind( ".gallery3d" );
881                                 }).on( touchLeaveEvt, function ( e ) {
882                                         view.trigger( touchEndEvt );
883                                 });
884                         });
885                 },
886
887                 // ----------------------------------------------------------
888                 // WebGL
889                 // ----------------------------------------------------------
890                 _initGL: function ( canvas ) {
891                         var self = this,
892                                 mat4 = glMatrix.mat4,
893                                 gl;
894
895                         gl = getContext3D( canvas );
896                         if ( !gl ) {
897                                 return null;
898                         }
899
900                         gl.enable( gl.BLEND );
901                         gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
902
903                         gl.enable( gl.DEPTH_TEST );
904                         gl.depthFunc( gl.LEQUAL );
905
906                         canvas.width = self._VIEWPORT_WIDTH;
907                         canvas.height = self._VIEWPORT_HEIGHT;
908                         gl.viewportWidth = canvas.width;
909                         gl.viewportHeight = canvas.height;
910                         gl.viewport( 0, 0, gl.viewportWidth, gl.viewportHeight );
911                         self._pMatrix = mat4.create();
912                         mat4.perspective( 40, gl.viewportWidth / gl.viewportHeight, 0.1, 10000.0, self._pMatrix );
913
914                         gl.clearColor( 0.15, 0.15, 0.15, 1.0 );
915                         gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
916
917                         return gl;
918                 },
919
920                 _initShader : function ( gl ) {
921                         var self = this,
922                                 shaderProgram;
923
924                         shaderProgram = $.webgl.shader.addShaderProgram( self._gl, VERTEX_SHADER, FRAGMENT_SHADER );
925                         gl.useProgram( shaderProgram );
926
927                         shaderProgram.vertexPositionAttr = gl.getAttribLocation( shaderProgram, "aVertexPosition" );
928                         gl.enableVertexAttribArray( shaderProgram.vertexPositionAttr );
929
930                         shaderProgram.textureCoordAttr = gl.getAttribLocation( shaderProgram, "aTextureCoord" );
931                         gl.enableVertexAttribArray( shaderProgram.textureCoordAttr );
932
933                         // Set light normal vectors for lighting~
934                         shaderProgram.vertexNormalAttr = gl.getAttribLocation( shaderProgram, "aVertexNormal" );
935                         gl.enableVertexAttribArray( shaderProgram.vertexNormalAttr );
936
937                         shaderProgram.perspectiveMU = gl.getUniformLocation( shaderProgram, "uPerspectiveMatrix");
938                         shaderProgram.transformMU = gl.getUniformLocation( shaderProgram, "uMoveMatrix");
939                         shaderProgram.sampleUniform = gl.getUniformLocation( shaderProgram, "uSampler");
940
941                         // Set light variables~
942                         shaderProgram.normalMU = gl.getUniformLocation( shaderProgram, "nNormalMatrix");
943                         shaderProgram.ambientColorU = gl.getUniformLocation( shaderProgram, "uAmbientColor");
944                         shaderProgram.lightDirU_first = gl.getUniformLocation( shaderProgram, "uLightDirection_first");
945                         shaderProgram.lightDirU_second = gl.getUniformLocation( shaderProgram, "uLightDirection_second");
946                         shaderProgram.directionColorU = gl.getUniformLocation( shaderProgram, "uDirectionColor");
947
948                         return shaderProgram;
949                 },
950
951                 _initBuffers: function ( gl, shaderProgram ) {
952                         var self = this,
953                                 i = 0,
954                                 mashBase = 0,
955                                 vertices = [],
956                                 textureCoords = [],
957                                 normalVectors = [],
958                                 nodes = [],
959                                 maxDrawLength = self._MAX_ITEM_COUNT;
960
961                         for ( i = 0; i < self._imageList.length + 1; i += 1 ) {
962                                 nodes[i] = new Node();
963                                 $.merge( vertices, nodes[i].vertices );
964                                 $.merge( textureCoords, nodes[i].textureCoords );
965                                 $.merge( normalVectors, nodes[i].normalVectors );
966
967                                 nodes[i].textureBuffer = gl.createBuffer();
968                                 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, nodes[i].textureBuffer );
969                                 mashBase = i * 4;
970                                 nodes[i].meshOrder = [
971                                         mashBase, mashBase + 1, mashBase + 2,
972                                         mashBase + 2, mashBase + 3, mashBase
973                                 ];
974                                 gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new GlArray16( nodes[i].meshOrder ), gl.STATIC_DRAW );
975                                 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null ); // release buffer memory
976                                 nodes[i].textureBufferItemSize = 6;
977                         }
978
979                         self._positionBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( vertices ) );
980                         self._positionBuffer.itemSize = 3;
981
982                         self._textureCoordBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( textureCoords ) );
983                         self._textureCoordBuffer.itemSize = 2;
984
985                         self._normalVectorBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( normalVectors ) ); // Vertex's normal vector for Direction light
986                         self._normalVectorBuffer.itemSize = 3;
987
988                         // Ambient light
989                         gl.uniform3f( shaderProgram.ambientColorU, 0.1, 0.1, 0.1 );
990                         // Direcntion light
991                         gl.uniform3f( shaderProgram.directionColorU, 1.0, 1.0, 1.0 );
992
993                         return nodes;
994                 },
995
996                 // ----------------------------------------------------------
997                 // Texture
998                 // ----------------------------------------------------------
999                 _initTextures: function ( gl, nodes ) {
1000                         var self = this;
1001
1002                         $( nodes ).each( function ( i ) {
1003                                 var node = nodes[i],
1004                                         url;
1005
1006                                 if ( !self._imageList[i] ) {
1007                                         return false;
1008                                 }
1009
1010                                 url = self._imageList[i].src;
1011                                 node.texture = gl.createTexture();
1012                                 self._loadImage( url, i, i, gl, nodes );
1013                         });
1014                 },
1015
1016                 _loadImage: function ( url, i, imageID, gl, nodes ) {
1017                         var self = this,
1018                                 isMipmap = false,
1019                                 image,
1020                                 node;
1021
1022                         gl = gl || self._gl;
1023                         nodes = nodes || self._nodes;
1024                         isMipmap = isMipmap || false;
1025                         node = nodes[i];
1026                         node.image = node.image || new Image();
1027
1028                         $( node.image ).one( "load", function ( e ) {
1029                                 self._bindTexture( gl, node, this, isMipmap );
1030                                 node.imageID = imageID;
1031
1032                                 if ( !self._animationID ) {
1033                                         self._setPosition( 0, 0 );
1034                                 }
1035                         });
1036
1037                         if ( self.options.thumbnailCache ) {
1038                                 $.imageloader.getThumbnail( url, function ( result ) {
1039                                         if ( result === "NOT_FOUND_ERR" ) {
1040                                                 $.imageloader.setThumbnail( url, function ( result ) {
1041                                                         if ( result && result.length > 30 ) {
1042                                                                 node.image.src = result;
1043                                                                 isMipmap = true;
1044                                                         } else {
1045                                                                 node.image.src = url;
1046                                                         }
1047                                                 });
1048                                         } else if ( result && result.length > 30 ) {
1049                                                 node.image.src = result;
1050                                                 isMipmap = true;
1051                                         } else {
1052                                                 node.image.src = url;
1053                                         }
1054                                 });
1055                         } else {
1056                                 node.image.src = url;
1057                         }
1058                 },
1059
1060                 _bindTexture: function ( gl, node, image, isMipmap ) {
1061                         if ( !node || !node.texture ) {
1062                                 return;
1063                         }
1064
1065                         gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
1066
1067                         gl.bindTexture( gl.TEXTURE_2D, node.texture );
1068                         gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
1069
1070                         if ( isMipmap ) {
1071                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
1072                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST );
1073                                 gl.generateMipmap( gl.TEXTURE_2D );
1074                         } else {
1075                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
1076                                 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
1077                         }
1078
1079                         gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
1080                         gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
1081
1082                         node.texture.loaded = true;
1083
1084                         // release texture memory
1085                         gl.bindTexture( gl.TEXTURE_2D, null );
1086                 },
1087
1088                 // ----------------------------------------------------------
1089                 // rendering
1090                 // ----------------------------------------------------------
1091                 _setPosition: function ( progress, direction ) {
1092                         var self = this,
1093                                 mat4 = glMatrix.mat4,
1094                                 nodes = self._nodes,
1095                                 imageList = self._imageList,
1096                                 imageListLength = imageList.length,
1097                                 itemCount = self._MAX_ITEM_COUNT,
1098                                 displayLength = ( imageListLength > itemCount ) ? itemCount : imageListLength,
1099                                 nextLevelLenth = 0,
1100                                 i = 0,
1101                                 t = 0,
1102                                 position = 0,
1103                                 angle = 0,
1104                                 current = 0,
1105                                 next = 0,
1106                                 nextLevel = 0,
1107                                 path = self._path,
1108                                 nextImageID = 0;
1109
1110                         nextLevelLenth = ( direction >= 0 ) ? displayLength + 1 : displayLength;
1111
1112                         if ( !nodes[i].level ) {
1113                                 nodes[i].level = displayLength;
1114                         }
1115
1116                         for ( i = 0; i < displayLength; i += 1 ) {
1117                                 if ( !nodes[i].mvMatrix ) {
1118                                         nodes[i].mvMatrix = mat4.create();
1119                                 }
1120
1121                                 if ( direction > 0 && nodes[i].level >= displayLength ) {
1122                                         nodes[i].level = 0;
1123                                 }
1124
1125                                 current = path.levels[nodes[i].level];
1126                                 nextLevel = ( nodes[i].level + nextLevelLenth + direction ) % nextLevelLenth;
1127                                 next = path.levels[nextLevel];
1128
1129                                 if ( imageListLength > itemCount ) {
1130                                         if ( direction > 0 && nextLevel === 1
1131                                                         && self._firstImageNumber !== nodes[i].imageID ) {
1132                                                 self._loadImage( imageList[self._firstImageNumber].src, i, self._firstImageNumber );
1133                                         } else if ( direction < 0 && nextLevel === nextLevelLenth - 1
1134                                                         && self._lastImageNumber !== nodes[i].imageID ) {
1135                                                 self._loadImage( imageList[self._lastImageNumber].src, i, self._lastImageNumber );
1136                                         }
1137                                 }
1138
1139                                 mat4.identity( nodes[i].mvMatrix );
1140                                 mat4.translate( nodes[i].mvMatrix, [-2.0, -2.0, 1.0] );
1141                                 mat4.rotate( nodes[i].mvMatrix, degreeToRadian( 19 ), [1, 0, 0] );
1142
1143                                 t = ( current + ( next - current ) * ( ( progress > 1 ) ? 1 : progress ) );
1144
1145                                 if ( progress >= self._ANIMATION_END ) {
1146                                         nodes[i].level = nextLevel || displayLength;
1147                                         t = path.levels[nodes[i].level];
1148                                 }
1149
1150                                 if ( ( progress < self._ANIMATION_END )
1151                                                 && ( direction <= 0 && nodes[i].level < 1 ) ) {
1152                                         nodes[i].drawable = false;
1153                                 } else {
1154                                         nodes[i].drawable = true;
1155                                 }
1156
1157                                 if ( progress === self._ANIMATION_END && nodes[i].level === 1 ) {
1158                                         self.element.trigger( "select", imageList[ nodes[i].imageID ], nodes[i].imageID );
1159                                 }
1160
1161                                 position = path.getPosition( t );
1162                                 angle = path.getAngle( t );
1163
1164                                 mat4.translate( nodes[i].mvMatrix, position );
1165                                 mat4.rotate( nodes[i].mvMatrix, angle, [0, 1, 0] );
1166                         }
1167
1168                         if ( imageListLength > itemCount && progress >= self._ANIMATION_END ) {
1169                                 self._firstImageNumber = ( self._firstImageNumber - direction ) % imageListLength;
1170                                 if ( self._firstImageNumber < 0 ) {
1171                                         self._firstImageNumber = imageListLength - 1;
1172                                 }
1173
1174                                 self._lastImageNumber = ( self._lastImageNumber - direction ) % imageListLength;
1175                                 if ( self._lastImageNumber < 0 ) {
1176                                         self._lastImageNumber = imageListLength - 1;
1177                                 }
1178                         }
1179                         self._drawScene();
1180                 },
1181
1182                 _drawScene: function () {
1183                         if ( !this._gl || !this._shaderProgram ) {
1184                                 return;
1185                         }
1186
1187                         var self = this,
1188                                 gl = self._gl,
1189                                 shaderProgram = self._shaderProgram,
1190                                 nodes = self._nodes,
1191                                 nodesLength = nodes.length,
1192                                 i;
1193
1194                         gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
1195
1196                         gl.bindBuffer( gl.ARRAY_BUFFER, self._positionBuffer );
1197                         gl.vertexAttribPointer( shaderProgram.vertexPositionAttr, self._positionBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1198
1199                         gl.bindBuffer( gl.ARRAY_BUFFER, self._textureCoordBuffer );
1200                         gl.vertexAttribPointer( shaderProgram.textureCoordAttr, self._textureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1201
1202                         gl.bindBuffer( gl.ARRAY_BUFFER, self._normalVectorBuffer );
1203                         gl.vertexAttribPointer( shaderProgram.vertexNormalAttr, self._normalVectorBuffer.itemSize, gl.FLOAT, false, 0, 0 );
1204
1205                         for ( i = 0; i < nodesLength; i += 1 ) {
1206                                 if ( nodes[i].drawable ) {
1207                                         self._drawElement( self._pMatrix, nodes[i] );
1208                                 }
1209                         }
1210                 },
1211
1212                 _drawElement: function ( perspectiveMatrix, targetNode ) {
1213                         var self = this,
1214                                 gl = self._gl,
1215                                 vec3 = glMatrix.vec3,
1216                                 mat3 = glMatrix.mat3,
1217                                 mat4 = glMatrix.mat4,
1218                                 shaderProgram = self._shaderProgram,
1219                                 moveMatrix = targetNode.mvMatrix,
1220                                 texture = targetNode.texture,
1221                                 meshIndexBuffer = targetNode.textureBuffer,
1222                                 meshIndexBufferItemSize = targetNode.textureBufferItemSize,
1223                                 lightPositions = self._lightsPositionStack,
1224                                 LightDir,
1225                                 normalMatrix;
1226
1227                         if ( !moveMatrix ) {
1228                                 return;
1229                         }
1230
1231                         gl.activeTexture( gl.TEXTURE0 );
1232                         if ( texture && texture.loaded ) {
1233                                 gl.bindTexture( gl.TEXTURE_2D, texture );
1234                         }
1235                         gl.uniform1i( shaderProgram.sampleUniform, 0 );
1236
1237                         LightDir = vec3.create();
1238                         vec3.normalize( lightPositions[0], LightDir );
1239                         vec3.scale( LightDir, -8 );
1240                         gl.uniform3fv( shaderProgram.lightDirU_first, LightDir );
1241
1242                         vec3.normalize( lightPositions[1], LightDir );
1243                         vec3.scale( LightDir, -1 );
1244                         gl.uniform3fv( shaderProgram.lightDirU_second, LightDir );
1245                         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, meshIndexBuffer );
1246
1247                         gl.uniformMatrix4fv( shaderProgram.perspectiveMU, false, perspectiveMatrix );
1248                         gl.uniformMatrix4fv( shaderProgram.transformMU, false, moveMatrix );
1249
1250                         normalMatrix = mat3.create();
1251                         mat4.toInverseMat3( moveMatrix, normalMatrix );
1252                         mat3.transpose( normalMatrix );
1253                         gl.uniformMatrix3fv( shaderProgram.normalMU, false, normalMatrix );
1254
1255                         gl.drawElements( gl.TRIANGLES, meshIndexBufferItemSize, gl.UNSIGNED_SHORT, 0 );
1256
1257                         // release buffer memory
1258                         gl.bindBuffer( gl.ARRAY_BUFFER, null );
1259                         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
1260
1261                         // release texture memory
1262                         gl.bindTexture( gl.TEXTURE_2D, null );
1263                 },
1264
1265                 // ----------------------------------------------------------
1266                 // Animation
1267                 // ----------------------------------------------------------
1268                 _animate: function ( easingType, duration, direction, repeatCount, startValue, _removeCount ) {
1269                         var self = this,
1270                                 timeNow = $.now(),
1271                                 progress,
1272                                 removeCount = 0;
1273
1274                         easingType = easingType || "linear";
1275                         startValue = startValue || 0;
1276                         _removeCount = _removeCount || 0;
1277
1278                         if ( self._sumTime >= duration ) {
1279                                 self._setPosition( self._ANIMATION_END, direction );
1280                                 self._stop();
1281                                 return;
1282                         }
1283
1284                         if ( self._startTime === 0 ) {
1285                                 self._startTime = timeNow;
1286                         } else {
1287                                 self._sumTime = timeNow - self._startTime;
1288                                 progress = $.easing[ easingType ]( self._sumTime / duration, self._sumTime, startValue, repeatCount + 1, duration );
1289                                 removeCount = parseInt( Math.abs( progress ), 10 );
1290
1291                                 if ( _removeCount !== removeCount ) {
1292                                         self._setPosition( self._ANIMATION_END, direction );
1293                                         _removeCount = removeCount;
1294
1295                                         if ( ( repeatCount - _removeCount ) >= 0 ) {
1296                                                 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
1297                                         } else {
1298                                                 self._stop();
1299                                         }
1300                                         return;
1301                                 }
1302
1303                                 self._setPosition( progress - _removeCount, direction );
1304                         }
1305
1306                         self._animationID = requestAnimationFrame( function () {
1307                                 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
1308                         });
1309                 },
1310
1311                 _run: function ( direction, repeatCount, startValue ) {
1312                         var self = this,
1313                                 repeat = repeatCount || 0,
1314                                 duration = self._DURATION_DEFAULT * ( repeat + 1 );
1315
1316                         if ( self._imageList.length <= 1 ) {
1317                                 return;
1318                         }
1319
1320                         startValue = startValue || 0;
1321                         duration = ( duration >= 0 ) ? duration : 0;
1322
1323                         if ( self._animationID ) {
1324                                 self._setPosition( self._ANIMATION_END, direction );
1325                                 self._stop();
1326                         }
1327
1328                         self._animate( "easeOutExpo", duration, direction, repeat, startValue );
1329                 },
1330
1331                 _reset: function () {
1332                         if ( !this._canvas || !this._gl ) {
1333                                 return;
1334                         }
1335
1336                         this._final();
1337                         this._init();
1338                         this.refresh();
1339                 },
1340
1341                 _stop: function () {
1342                         if ( this._animationID ) {
1343                                 cancelAnimationFrame( this._animationID );
1344                         }
1345                         this._animationID = 0;
1346
1347                         this._startTime = 0;
1348                         this._sumTime = 0;
1349                 },
1350
1351                 next: function () {
1352                         this._run( this._DIRECTION_LEFT , 0 );
1353                 },
1354
1355                 prev: function () {
1356                         this._run( this._DIRECTION_RIGHT, 0 );
1357                 },
1358
1359                 refresh: function () {
1360                         var view = this.element,
1361                                 canvas = view.find( "canvas.ui-gallery3d-canvas" );
1362
1363                         if ( canvas.width() !== view.width() ) {
1364                                 canvas.width( view.width() );
1365                         }
1366
1367                         if ( !this._animationID ) {
1368                                 this._setPosition( 0, 0 );
1369                         }
1370                 },
1371
1372                 select: function ( index ) {
1373                         var nodes = this._nodes,
1374                                 repeat,
1375                                 i,
1376                                 imageID,
1377                                 object = null,
1378                                 target = 0,
1379                                 direction = 0;
1380
1381                         if ( index && this._animationID ) {
1382                                 this._stop();
1383                         }
1384
1385                         for ( i in nodes ) {
1386                                 if ( nodes[i].level === 1 ) {
1387                                         object = this._imageList[ nodes[i].imageID ];
1388                                         imageID = nodes[i].imageID;
1389                                         break;
1390                                 }
1391                         }
1392
1393                         if ( !index ) {
1394                                 return object;
1395                         }
1396
1397                         if ( index < 0 && index >= this._imageList.length ) {
1398                                 return;
1399                         }
1400
1401                         target = index - imageID;
1402                         direction = ( target > 0 ) ? this._DIRECTION_LEFT
1403                                 : ( ( target < 0 ) ? this._DIRECTION_RIGHT : 0 );
1404                         if ( direction ) {
1405                                 this._run( direction, Math.abs( target ) - 1  );
1406                         }
1407                 },
1408
1409                 add: function ( item, index ) {
1410                         if ( !item ) {
1411                                 return;
1412                         }
1413
1414                         if ( typeof item === "string" ) {
1415                                 item = { "src" : item };
1416                         }
1417
1418                         index = index || 0;
1419                         if ( typeof index !== "number" && index < 0
1420                                         && index >= this._imageList.length ) {
1421                                 return;
1422                         }
1423
1424                         this._imageList.splice( index, 0, item );
1425                         if ( this._gl ) {
1426                                 this._reset();
1427                         }
1428                 },
1429
1430                 remove: function ( index ) {
1431                         index = index || 0;
1432                         if ( typeof index !== "number" && index < 0
1433                                         && index >= this._imageList.length ) {
1434                                 return;
1435                         }
1436
1437                         this._imageList.splice( index, 1 );
1438                         if ( this._gl ) {
1439                                 this._reset();
1440                         }
1441                 },
1442
1443                 clearThumbnailCache: function () {
1444                         if ( !this._nodes || ( this._nodes.length <= 0 ) ) {
1445                                 return;
1446                         }
1447
1448                         var i, url;
1449                         for ( i = 0; i < this._imageList.length; i += 1 ) {
1450                                 url = this._imageList[i].src;
1451                                 $.imageloader.removeThumbnail( url );
1452                         }
1453                 },
1454
1455                 empty: function () {
1456                         this._imageList = [];
1457                         this._reset();
1458                 },
1459
1460                 length: function () {
1461                         return this._imageList.length;
1462                 }
1463         });
1464
1465         $( document ).on( "pagecreate create", function ( e ) {
1466                 $( ":jqmData(role='gallery3d')" ).gallery3d();
1467         }).on( "pagechange", function ( e ) {
1468                 $( e.target ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
1469         });
1470
1471         $( window ).on( "resize orientationchange", function ( e ) {
1472                 $( ".ui-page-active" ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
1473         });
1474
1475 } ( jQuery, document, window ) );
1476
1477 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
1478 } );
1479 //>>excludeEnd("jqmBuildExclude");