1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
\r
2 //>>description: 3D photo gallery widget.
\r
4 //>>group: Tizen:Widgets
\r
6 define( [ "components/imageloader", "components/motionpath", "components/webgl" ], function ( ) {
\r
7 //>>excludeEnd("jqmBuildExclude");
\r
10 /* ***************************************************************************
\r
11 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
\r
13 * Permission is hereby granted, free of charge, to any person obtaining a
\r
14 * copy of this software and associated documentation files (the "Software"),
\r
15 * to deal in the Software without restriction, including without limitation
\r
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
\r
17 * and/or sell copies of the Software, and to permit persons to whom the
\r
18 * Software is furnished to do so, subject to the following conditions:
\r
20 * The above copyright notice and this permission notice shall be included in
\r
21 * all copies or substantial portions of the Software.
\r
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
\r
28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
\r
29 * DEALINGS IN THE SOFTWARE.
\r
30 * ***************************************************************************
\r
32 * Authors: Hyunsook Park <hyunsook.park@samsung.com>
\r
33 * Wonseop Kim <wonseop.kim@samsung.com>
\r
37 * 'Gallery3D' is a 3D photo gallery widget.
\r
38 * Images are arranged with a S-shaped curve on a 3-dimensional coordinate system.
\r
39 * A user can rotate images by swiping the widget area.
\r
40 * To improve performance, the size of image(s) displayed on the screen should be a square(under
\r
41 * 128X128 pixel) as possible. But if a user can't resize the images, this widget supports an image
\r
42 * resizing feature and he/she can use it with "data-thumbnail-cache" option. ("data-thumbnail-cache"
\r
43 * option resizes the gallery images under 128x128 pixels and stores the images on a local storage.
\r
44 * So when a gallery3D widget is re-launched, the widget reuse the storage and a user can improve
\r
45 * launching time. A browser or web runtime engine should support "Web Storage" feature to use that
\r
50 * data-thumbnail-cache : Determines whether to cache and resize images.
\r
55 * : This method moves each image forward one by one.
\r
57 * : This method moves each image backward one by one.
\r
58 * select ( [number] )
\r
59 * : When the "select" method is called with an argument, the method selects the image of given index.
\r
60 * If the method is called with no argument, it will return the Javascript object having "src"
\r
61 * attribute having the selected image’s URL.
\r
62 * add ( object or string [, number] )
\r
63 * This method adds an image to Gallery3D widget.
\r
64 * If the second argument isn't inputted, the image is added at the 0th position.
\r
65 * remove ( [number] )
\r
66 * : This method deletes an image from Gallery3d widget.
\r
67 * The argument defines the index of the image to be deleted.
\r
68 * If an argument isn't inputted, it removes current image.
\r
69 * clearThumbnailCache ( void )
\r
70 * : This method clears the cache data of all images when thumbnailCache option is set as 'true'.
\r
72 * : This method updates and redraws current widget.
\r
74 * : This method removes all of images from Gallery3D widget.
\r
76 * : This method gets the number of images.
\r
80 * select : Triggered when an image is selected.
\r
85 * $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
86 * $( "#gallery3d" ).gallery3d( "add", "01.jpg" );
\r
89 * <div id="gallery3d" data-role="gallery3d"></div>
\r
94 The gallery3d widget is a 3D photo gallery widget.
\r
95 Images are arranged with a S-shaped curve on a 3-dimensional coordinate system.
\r
96 A user can rotate images by swiping the widget area.
\r
97 <br/><br/>To add an gallery3d widget to the application, use the following code:
\r
100 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
101 $( "#gallery3d" ).gallery3d( "add", "01.jpg" );
\r
104 <div id="gallery3d" data-role="gallery3d"></div>
\r
107 @property {Boolean} data-thumbnail-cache
\r
108 Determines whether to cache and resize images.
\r
109 To improve performance, the size of image(s) displayed on the screen should be a square (under 128X128 pixels).
\r
110 "data-thumbnail-cache" option resizes the gallery images under 128x128 pixels and stores the images on a local storage.
\r
111 So when a gallery3D widget is re-launched, the widget reuses the storage and the launching time can be improved.
\r
112 A browser or web runtime engine must support "Web Storage" feature to use this option.
\r
116 Triggered when an image is selected.
\r
119 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
120 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
121 .gallery3d( "add", { src: "2.jpg" } )
\r
122 .gallery3d( "add", { src: "3.jpg" } );
\r
123 }).on( "select", function ( event, data, index ) {
\r
124 // Handle the select event
\r
125 var urlOfImage = data.src, indexOfImage = index;
\r
128 <div id="gallery3d" data-role="gallery3d"></div>
\r
132 This method moves each image forward one by one.
\r
135 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
136 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
137 .gallery3d( "add", { src: "2.jpg" } )
\r
138 .gallery3d( "add", { src: "3.jpg" } )
\r
139 .gallery3d( "next" );
\r
142 <div id="gallery3d" data-role="gallery3d"></div>
\r
146 This method moves each image backward one by one.
\r
149 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
150 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
151 .gallery3d( "add", { src: "2.jpg" } )
\r
152 .gallery3d( "add", { src: "3.jpg" } )
\r
153 .gallery3d( "prev" );
\r
156 <div id="gallery3d" data-role="gallery3d"></div>
\r
160 When the "select" method is called with an argument, the method selects the image of given index.
\r
161 If the method is called with no argument, it will return the Javascript object having "src" attribute having the selected image’s URL.
\r
164 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
165 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
166 .gallery3d( "add", { src: "2.jpg" } )
\r
167 .gallery3d( "add", { src: "3.jpg" } );
\r
168 var selectedImage = $("#gallery3d"). gallery3d( "select" );
\r
169 // selectedImage = { src: "3.jpg" };
\r
172 <div id="gallery3d" data-role="gallery3d"></div>
\r
176 This method adds an image to Gallery3D widget.
\r
177 The first argument is a Javascript object having a "src" attribute or a string of image's path.
\r
178 The second argument is an index of images.
\r
179 If second argument isn't inputted, the image is added at the 0th position.
\r
182 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
183 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
184 .gallery3d( "add", "2.jpg", 1 );
\r
187 <div id="gallery3d" data-role="gallery3d"></div>
\r
191 This method deletes an image from Gallery3d widget.
\r
192 The argument defines the index of the image to be deleted.
\r
193 If an argument isn't inputted, it removes current image.
\r
196 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
197 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
198 .gallery3d( "add", { src: "2.jpg" } )
\r
199 .gallery3d( "add", { src: "3.jpg" } );
\r
201 $( "#gallery3d" ).gallery3d( "remove" );
\r
202 $( "#gallery3d" ).gallery3d( "remove", 1 );
\r
205 <div id="gallery3d" data-role="gallery3d"></div>
\r
208 @method clearThumbnailCache
\r
209 This method clears the cache data of all images when thumbnailCache option is set as 'true'
\r
212 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
213 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
214 .gallery3d( "add", { src: "2.jpg" } )
\r
215 .gallery3d( "add", { src: "3.jpg" } );
\r
217 $( "#gallery3d" ).gallery3d( "clearThumbnailCache" );
\r
220 <div id="gallery3d" data-role="gallery3d" data-thumbnail-cache="true"></div>
\r
224 This method updates and redraws current widget.
\r
227 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
228 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
229 .gallery3d( "add", { src: "2.jpg" } )
\r
230 .gallery3d( "add", { src: "3.jpg" } );
\r
232 $( "#gallery3d" ).gallery3d( "refresh" );
\r
235 <div id="gallery3d" data-role="gallery3d"></div>
\r
239 This method removes all of images from Gallery3D widget.
\r
242 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
243 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
244 .gallery3d( "add", { src: "2.jpg" } )
\r
245 .gallery3d( "add", { src: "3.jpg" } );
\r
247 $( "#gallery3d" ).gallery3d( "empty" );
\r
250 <div id="gallery3d" data-role="gallery3d"></div>
\r
254 This method gets the number of images.
\r
257 $( "#gallery3d" ).on( "gallery3dcreate", function () {
\r
258 $( "#gallery3d" ).gallery3d( "add", { src: "1.jpg" } )
\r
259 .gallery3d( "add", { src: "2.jpg" } )
\r
260 .gallery3d( "add", { src: "3.jpg" } );
\r
262 var imagesLength = $( "#gallery3d" ).gallery3d( "length" );
\r
263 // imagesLength = 3;
\r
266 <div id="gallery3d" data-role="gallery3d"></div>
\r
269 ( function ( $, document, window, undefined ) {
\r
270 window.requestAnimationFrame = ( function () {
\r
271 return function ( callback ) {
\r
272 var id = window.setTimeout( callback, 1000 / 60 );
\r
277 window.cancelAnimationFrame = ( function () {
\r
278 return function ( id ) {
\r
279 window.clearTimeout( id );
\r
283 var vec3 = window.vec3,
\r
284 mat3 = window.mat3,
\r
285 mat4 = window.mat4,
\r
286 GlArray32 = ( typeof window.Float32Array !== "undefined" ? window.Float32Array : ( typeof window.WebGLFloatArray !== "undefined" ? window.WebGLFloatArray : Array ) ),
\r
287 GlArray16 = ( typeof window.Uint16Array !== "undefined" ? window.Uint16Array : Array ),
\r
288 getContext3D = function ( canvas ) {
\r
290 contextNames = [ "experimental-webgl", "webkit-3d", "webgl", "moz-webgl" ];
\r
292 for ( i = 0; i < contextNames.length; i += 1 ) {
\r
294 gl = canvas.getContext( contextNames[i] );
\r
299 window.alert( "Unfortunately, there's a WebGL compatibility problem. </br> You may want to check your system settings." );
\r
306 "attribute vec3 aVertexPosition;",
\r
307 "attribute vec2 aTextureCoord;",
\r
308 "attribute vec3 aVertexNormal;",
\r
309 "uniform mat4 uMoveMatrix;",
\r
310 "uniform mat4 uPerspectiveMatrix;",
\r
311 "uniform mat3 nNormalMatrix;",
\r
312 "uniform vec3 uAmbientColor;",
\r
313 "uniform vec3 uLightDirection;",
\r
314 "uniform vec3 uDirectionColor;",
\r
315 "uniform vec3 uLightDirection_first;",
\r
316 "uniform vec3 uLightDirection_second;",
\r
317 "varying vec2 vTextureCoord;",
\r
318 "varying vec3 vLightWeight;",
\r
319 "varying vec4 vFogWeight;",
\r
321 "void main(void) {",
\r
322 " vec4 v_Position = uMoveMatrix * vec4(aVertexPosition, 1.0);",
\r
323 " gl_Position = uPerspectiveMatrix * v_Position;",
\r
324 " vTextureCoord = aTextureCoord;",
\r
325 " float fog = 1.0 - ((gl_Position.z + 1.5) / 60.0);",
\r
326 " vFogWeight = clamp( vec4( fog, fog, fog, 1.0), 0.0, 1.0);",
\r
327 " vec3 transNormalVector = nNormalMatrix * aVertexNormal;",
\r
329 " float vLightWeightFirst = 0.0;",
\r
330 " float vLightWeightSecond = max( dot(transNormalVector, uLightDirection_second), 0.0 );",
\r
332 " vLightWeight = uAmbientColor + uDirectionColor * vLightWeightSecond;",
\r
335 FRAGMENT_SHADER = [
\r
336 "precision mediump float;",
\r
337 "varying vec2 vTextureCoord;",
\r
338 "varying vec3 vLightWeight;",
\r
339 "uniform sampler2D uSampler;",
\r
340 "varying vec4 vFogWeight;",
\r
342 "void main(void) {",
\r
343 " vec4 TextureColor = (texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t))) * vFogWeight;",
\r
344 " gl_FragColor = vec4(TextureColor.rgb * vLightWeight, TextureColor.a);",
\r
355 this.textureCoords = [
\r
361 this.normalVectors = [
\r
367 this.texture = null;
\r
368 this.textureBuffer = null;
\r
369 this.textureBufferItemSize = 0;
\r
370 this.mashOrder = [];
\r
371 this.mvMatrix = null;
\r
373 this.targetLevel = 0;
\r
374 this.drawable = false;
\r
379 $.widget( "tizen.gallery3d", $.mobile.widget, {
\r
381 thumbnailCache: false
\r
384 _MAX_ITEM_COUNT: 28,
\r
385 _ANIMATION_END: 999,
\r
386 _DURATION_DEFAULT: 300,
\r
387 _DURATION_FIRST: 1600,
\r
388 _VIEWPORT_WIDTH: 1024,
\r
389 _VIEWPORT_HEIGHT: 456,
\r
390 _DIRECTION_LEFT: -1,
\r
391 _DIRECTION_RIGHT: +1,
\r
394 _shaderProgram : null,
\r
395 _positionBuffer : null,
\r
396 _textureCoordBuffer : null,
\r
397 _normalVectorBuffer : null,
\r
404 _lightsPositionStack : [
\r
405 [0.0, 0.0, -1.0], // back
\r
406 [-0.2, 0.0, 0.7] // front
\r
409 _swipeThresholdOfBasetimeGap: ( $.support.touch ? 30 : 70 ),
\r
410 _swipeThresholdOfSensitivity: ( $.support.touch ? 2.0 : 10.0 ),
\r
414 _firstImageNumber: 0,
\r
415 _lastImageNumber: 0,
\r
417 _create: function () {
\r
419 view = self.element,
\r
420 option = self.options;
\r
422 self._canvas = $( "<canvas class='ui-gallery3d-canvas'></canvas>" );
\r
424 view.addClass( "ui-gallery3d" ).append( self._canvas );
\r
425 self._addBehavier();
\r
427 self._dragInterval = 1000 / 30; // 30fps
\r
429 $.each( self.options, function ( key, value ) {
\r
430 self.options[ key ] = undefined;
\r
431 self._setOption( key, value );
\r
436 _setOption: function ( key, value ) {
\r
438 case "thumbnailCache" :
\r
439 if ( typeof value === "string" ) {
\r
440 value = ( value === "true" ) ? true : false;
\r
448 $.mobile.widget.prototype._setOption.call( this, key, value );
\r
451 _init: function ( canvas ) {
\r
455 [-12, 0, -40], // contorl Point of Point1
\r
456 [24, 0, -9], // contorl Point of Point2
\r
461 canvas = canvas || self._canvas;
\r
467 self._gl = self._gl || self._initGL( canvas[0] );
\r
472 if ( !self._imageList ) {
\r
476 self._shaderProgram = self._shaderProgram || self._initShader( self._gl );
\r
477 if ( !self._shaderProgram ) {
\r
481 if ( self._imageList.length > self._MAX_ITEM_COUNT ) {
\r
482 self._firstImageNumber = self._imageList.length - 1;
\r
483 self._lastImageNumber = self._MAX_ITEM_COUNT - 1;
\r
486 self._nodes = self._initBuffers( self._gl, self._shaderProgram );
\r
487 self._initTextures( self._gl, self._nodes );
\r
488 self._path = $.motionpath( "bspline", {
\r
489 points: pathPoints,
\r
490 maxLevel: self._MAX_ITEM_COUNT
\r
492 for ( i = 0; i < self._nodes.length; i += 1 ) {
\r
493 self._path.levels[i] = self._path.levels[i + 1] || 0;
\r
494 self._nodes[i].level = i;
\r
498 _final: function ( canvas ) {
\r
506 canvas = canvas || self._canvas;
\r
508 $( self._nodes ).each( function ( i ) {
\r
509 var node = self._nodes[i];
\r
510 gl.deleteTexture( node.texture );
\r
511 node.texture = null;
\r
513 self._nodes = null;
\r
515 gl.deleteBuffer( self._positionBuffer );
\r
516 self._positionBuffer = null;
\r
517 gl.deleteBuffer( self._textureCoordBuffer );
\r
518 self._textureCoordBuffer = null;
\r
519 gl.deleteBuffer( self._normalVectorBuffer );
\r
520 self._normalVectorBuffer = null;
\r
522 $.webgl.shader.deleteShaders( gl );
\r
523 gl.deleteProgram( self._shaderProgram );
\r
524 self._shaderProgram = null;
\r
526 self._gl = gl = null;
\r
529 _addBehavier : function () {
\r
531 view = self.element,
\r
532 canvas = self._canvas,
\r
533 touchStartEvt = ( $.support.touch ? "touchstart" : "mousedown" ),
\r
534 touchMoveEvt = ( $.support.touch ? "touchmove" : "mousemove" ) + ".gallery3d",
\r
535 touchEndEvt = ( $.support.touch ? "touchend" : "mouseup" ) + ".gallery3d",
\r
536 touchLeaveEvt = ( $.support.touch ? "touchleave" : "mouseout" ) + ".gallery3d";
\r
538 $( document ).unbind( ".gallery3d" ).bind( "pagechange.gallery3d", function ( e ) {
\r
539 $( e.target ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
\r
540 }).bind( "pageremove.gallery3d", function ( e ) {
\r
541 $( e.target ).find( ".ui-gallery3d" ).trigger( "_destory" );
\r
544 $( window ).unbind( ".gallery3d" ).bind( "resize.gallery3d orientationchange.gallery3d", function ( e ) {
\r
545 $( ".ui-page-active" ).find( ".ui-gallery3d" ).gallery3d( "refresh" );
\r
546 }).bind( "unload.gallery3d", function ( e ) {
\r
547 $( e.target ).find( ".ui-gallery3d" ).trigger( "_destory" );
\r
550 view.bind( "_destory", function ( e ) {
\r
554 canvas.bind( "webglcontextlost", function ( e ) {
\r
555 e.preventDefault();
\r
556 }).bind( "webglcontextrestored", function ( e ) {
\r
558 }).bind( touchStartEvt, function ( e ) {
\r
561 deltaMaxSteps = 20,
\r
562 deltas = [ deltaMaxSteps ],
\r
563 deltaTimes = [ deltaMaxSteps ],
\r
566 dragDirection = false,
\r
569 e.preventDefault();
\r
570 e.stopPropagation();
\r
572 if ( self._imageList.length <= 1 ) {
\r
578 startX = $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
\r
579 prevTime = $.now();
\r
581 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
\r
582 deltas[i] = startX;
\r
583 deltaTimes[i] = $.now();
\r
588 view.bind( touchMoveEvt, function ( e ) {
\r
589 var x, dx, interval;
\r
591 e.preventDefault();
\r
592 e.stopPropagation();
\r
594 x = $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
\r
597 deltas[deltaIndex] = x;
\r
598 deltaTimes[deltaIndex] = $.now();
\r
599 interval = deltaTimes[deltaIndex] - prevTime;
\r
601 deltaIndex = ( deltaIndex + 1 ) % deltaMaxSteps;
\r
603 // Validation of drag
\r
604 if ( Math.abs( dx ) >= 10 && interval >= self._dragInterval ) {
\r
605 if ( dragDirection !== ( ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT ) ) {
\r
607 dragDirection = ( dx < 0 ) ? self._DIRECTION_RIGHT : self._DIRECTION_LEFT;
\r
610 dragValue += Math.abs( dx ) / 100;
\r
611 if ( dragValue >= 1 ) {
\r
612 self._setPosition( self._ANIMATION_END, dragDirection );
\r
615 self._setPosition( dragValue, dragDirection );
\r
619 prevTime = $.now();
\r
621 }).bind( touchEndEvt, function ( e ) {
\r
634 e.preventDefault();
\r
635 e.stopPropagation();
\r
637 // Validation of swipe
\r
638 baseTime = $.now() - self._swipeThresholdOfBasetimeGap;
\r
639 lastX = $.support.touch ? e.originalEvent.changedTouches[0].pageX : e.pageX;
\r
640 dx = startX - lastX;
\r
642 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
\r
643 index = ( deltaIndex + i ) % deltaMaxSteps;
\r
644 if ( deltaTimes[index] > baseTime ) {
\r
649 if ( recent < 0 ) {
\r
655 for ( i = 0; i < deltaMaxSteps; i += 1 ) {
\r
656 previous = ( previous - 1 + deltaMaxSteps ) % deltaMaxSteps;
\r
657 if ( deltaTimes[previous] < deltaTimes[recent] ) {
\r
661 // too slow or too fast
\r
662 if ( i === deltaMaxSteps || baseTime < deltaTimes[previous] ) {
\r
668 baseTimeRatio = ( baseTime - deltaTimes[previous] ) / ( deltaTimes[recent] - deltaTimes[previous] );
\r
669 fx = ( 1.0 - baseTimeRatio ) * deltas[previous] + baseTimeRatio * deltas[recent];
\r
670 if ( Math.abs( fx - lastX ) < self._swipeThresholdOfSensitivity ) {
\r
673 velocityX = parseInt( ( lastX - fx ) / ( $.now() - baseTime ), 10 );
\r
676 if ( isSwipe && velocityX ) {
\r
677 direction = ( velocityX < 0 ) ? self._DIRECTION_LEFT : self._DIRECTION_RIGHT;
\r
678 self._run( direction, Math.abs( velocityX ), dragValue );
\r
679 } else if ( dragDirection !== 0 && dragValue ) {
\r
680 self._animate( null, self._DURATION_DEFAULT * ( 1 - dragValue ), dragDirection, 0, dragValue );
\r
683 view.unbind( ".gallery3d" );
\r
684 }).bind( touchLeaveEvt, function ( e ) {
\r
685 view.trigger( touchEndEvt );
\r
690 // ----------------------------------------------------------
\r
692 // ----------------------------------------------------------
\r
693 _loadData: function ( jsonUrl, key ) {
\r
700 success : function ( data ) {
\r
701 self._imageList = $.extend( [], data[ key ] );
\r
706 // ----------------------------------------------------------
\r
708 // ----------------------------------------------------------
\r
709 _initGL: function ( canvas ) {
\r
713 gl = getContext3D( canvas );
\r
715 window.alert( "There's no WebGL context available!!!" );
\r
719 gl.enable( gl.BLEND );
\r
720 gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
\r
722 gl.enable( gl.DEPTH_TEST );
\r
723 gl.depthFunc( gl.LEQUAL );
\r
725 canvas.width = self._VIEWPORT_WIDTH;
\r
726 canvas.height = self._VIEWPORT_HEIGHT;
\r
727 gl.viewportWidth = canvas.width;
\r
728 gl.viewportHeight = canvas.height;
\r
729 gl.viewport( 0, 0, gl.viewportWidth, gl.viewportHeight );
\r
730 self._pMatrix = mat4.create();
\r
731 mat4.perspective( 40, gl.viewportWidth / gl.viewportHeight, 0.1, 10000.0, self._pMatrix );
\r
733 gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
\r
734 gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
\r
739 _initShader : function ( gl ) {
\r
743 shaderProgram = $.webgl.shader.addShaderProgram( self._gl, VERTEX_SHADER, FRAGMENT_SHADER );
\r
744 gl.useProgram( shaderProgram );
\r
746 shaderProgram.vertexPositionAttr = gl.getAttribLocation( shaderProgram, "aVertexPosition" );
\r
747 gl.enableVertexAttribArray( shaderProgram.vertexPositionAttr );
\r
749 shaderProgram.textureCoordAttr = gl.getAttribLocation( shaderProgram, "aTextureCoord" );
\r
750 gl.enableVertexAttribArray( shaderProgram.textureCoordAttr );
\r
752 // Set light normal vectors for lighting~
\r
753 shaderProgram.vertexNormalAttr = gl.getAttribLocation( shaderProgram, "aVertexNormal" );
\r
754 gl.enableVertexAttribArray( shaderProgram.vertexNormalAttr );
\r
756 shaderProgram.perspectiveMU = gl.getUniformLocation( shaderProgram, "uPerspectiveMatrix");
\r
757 shaderProgram.transformMU = gl.getUniformLocation( shaderProgram, "uMoveMatrix");
\r
758 shaderProgram.sampleUniform = gl.getUniformLocation( shaderProgram, "uSampler");
\r
760 // Set light variables~
\r
761 shaderProgram.normalMU = gl.getUniformLocation( shaderProgram, "nNormalMatrix");
\r
762 shaderProgram.ambientColorU = gl.getUniformLocation( shaderProgram, "uAmbientColor");
\r
763 shaderProgram.lightDirU_first = gl.getUniformLocation( shaderProgram, "uLightDirection_first");
\r
764 shaderProgram.lightDirU_second = gl.getUniformLocation( shaderProgram, "uLightDirection_second");
\r
765 shaderProgram.directionColorU = gl.getUniformLocation( shaderProgram, "uDirectionColor");
\r
767 return shaderProgram;
\r
770 _initBuffers: function ( gl, shaderProgram ) {
\r
775 textureCoords = [],
\r
776 normalVectors = [],
\r
778 maxDrawLength = self._MAX_ITEM_COUNT;
\r
780 for ( i = 0; i < self._imageList.length + 1; i += 1 ) {
\r
781 nodes[i] = new Node();
\r
782 $.merge( vertices, nodes[i].vertices );
\r
783 $.merge( textureCoords, nodes[i].textureCoords );
\r
784 $.merge( normalVectors, nodes[i].normalVectors );
\r
786 nodes[i].textureBuffer = gl.createBuffer();
\r
787 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, nodes[i].textureBuffer );
\r
789 nodes[i].meshOrder = [
\r
790 mashBase, mashBase + 1, mashBase + 2,
\r
791 mashBase + 2, mashBase + 3, mashBase
\r
793 gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new GlArray16( nodes[i].meshOrder ), gl.STATIC_DRAW );
\r
794 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null ); // release buffer memory
\r
795 nodes[i].textureBufferItemSize = 6;
\r
798 self._positionBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( vertices ) );
\r
799 self._positionBuffer.itemSize = 3;
\r
801 self._textureCoordBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( textureCoords ) );
\r
802 self._textureCoordBuffer.itemSize = 2;
\r
804 self._normalVectorBuffer = $.webgl.buffer.attribBufferData( gl, new GlArray32( normalVectors ) ); // Vertex's normal vector for Direction light
\r
805 self._normalVectorBuffer.itemSize = 3;
\r
808 gl.uniform3f( shaderProgram.ambientColorU, 0.1, 0.1, 0.1 );
\r
809 // Direcntion light
\r
810 gl.uniform3f( shaderProgram.directionColorU, 1.0, 1.0, 1.0 );
\r
815 // ----------------------------------------------------------
\r
817 // ----------------------------------------------------------
\r
818 _initTextures: function ( gl, nodes ) {
\r
821 $( nodes ).each( function ( i ) {
\r
822 var node = nodes[i],
\r
825 if ( !self._imageList[i] ) {
\r
829 url = self._imageList[i].src;
\r
830 node.texture = gl.createTexture();
\r
831 self._loadImage( url, i, i, gl, nodes );
\r
835 _loadImage: function ( url, i, imageID, gl, nodes ) {
\r
841 gl = gl || self._gl;
\r
842 nodes = nodes || self._nodes;
\r
843 isMipmap = isMipmap || false;
\r
845 node.image = node.image || new Image();
\r
847 $( node.image ).one( "load", function ( e ) {
\r
848 self._bindTexture( gl, node, this, isMipmap );
\r
849 node.imageID = imageID;
\r
851 if ( !self._animationID ) {
\r
852 self._setPosition( 0, 0 );
\r
856 if ( self.options.thumbnailCache ) {
\r
857 $.imageloader.getThumbnail( url, function ( result ) {
\r
858 if ( result === "NOT_FOUND_ERR" ) {
\r
859 $.imageloader.setThumbnail( url, function ( result ) {
\r
860 if ( result && result.length > 30 ) {
\r
861 node.image.src = result;
\r
864 node.image.src = url;
\r
867 } else if ( result && result.length > 30 ) {
\r
868 node.image.src = result;
\r
871 node.image.src = url;
\r
875 node.image.src = url;
\r
879 _bindTexture: function ( gl, node, image, isMipmap ) {
\r
880 if ( !node || !node.texture ) {
\r
884 gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
\r
886 gl.bindTexture( gl.TEXTURE_2D, node.texture );
\r
887 gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
\r
890 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
\r
891 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST );
\r
892 gl.generateMipmap( gl.TEXTURE_2D );
\r
894 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
\r
895 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
\r
898 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
\r
899 gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
\r
901 node.texture.loaded = true;
\r
903 // release texture memory
\r
904 gl.bindTexture( gl.TEXTURE_2D, null );
\r
907 // ----------------------------------------------------------
\r
909 // ----------------------------------------------------------
\r
910 _setPosition: function ( progress, direction ) {
\r
912 nodes = self._nodes,
\r
913 imageList = self._imageList,
\r
914 imageListLength = imageList.length,
\r
915 itemCount = self._MAX_ITEM_COUNT,
\r
916 displayLength = ( imageListLength > itemCount ) ? itemCount : imageListLength,
\r
917 nextLevelLenth = 0,
\r
928 nextLevelLenth = ( direction >= 0 ) ? displayLength + 1 : displayLength;
\r
930 if ( !nodes[i].level ) {
\r
931 nodes[i].level = displayLength;
\r
934 for ( i = 0; i < displayLength; i += 1 ) {
\r
935 if ( !nodes[i].mvMatrix ) {
\r
936 nodes[i].mvMatrix = mat4.create();
\r
939 if ( direction > 0 && nodes[i].level >= displayLength ) {
\r
940 nodes[i].level = 0;
\r
943 current = path.levels[nodes[i].level];
\r
944 nextLevel = ( nodes[i].level + nextLevelLenth + direction ) % nextLevelLenth;
\r
945 next = path.levels[nextLevel];
\r
947 if ( imageListLength > itemCount ) {
\r
948 if ( direction > 0 && nextLevel === 1
\r
949 && self._firstImageNumber !== nodes[i].imageID ) {
\r
950 self._loadImage( imageList[self._firstImageNumber].src, i, self._firstImageNumber );
\r
951 } else if ( direction < 0 && nextLevel === nextLevelLenth - 1
\r
952 && self._lastImageNumber !== nodes[i].imageID ) {
\r
953 self._loadImage( imageList[self._lastImageNumber].src, i, self._lastImageNumber );
\r
957 mat4.identity( nodes[i].mvMatrix );
\r
958 mat4.translate( nodes[i].mvMatrix, [-2.0, -2.0, 1.0] );
\r
959 mat4.rotate( nodes[i].mvMatrix, self._degreeToRadian( 19 ), [1, 0, 0] );
\r
961 t = ( current + ( next - current ) * ( ( progress > 1 ) ? 1 : progress ) );
\r
963 if ( progress >= self._ANIMATION_END ) {
\r
964 nodes[i].level = nextLevel || displayLength;
\r
965 t = path.levels[nodes[i].level];
\r
968 if ( ( progress < self._ANIMATION_END )
\r
969 && ( direction <= 0 && nodes[i].level < 1 ) ) {
\r
970 nodes[i].drawable = false;
\r
972 nodes[i].drawable = true;
\r
975 if ( progress === self._ANIMATION_END && nodes[i].level === 1 ) {
\r
976 self.element.trigger( "select", imageList[ nodes[i].imageID ], nodes[i].imageID );
\r
979 position = path.getPosition( t );
\r
980 angle = path.getAngle( t );
\r
982 mat4.translate( nodes[i].mvMatrix, position );
\r
983 mat4.rotate( nodes[i].mvMatrix, angle, [0, 1, 0] );
\r
986 if ( imageListLength > itemCount && progress >= self._ANIMATION_END ) {
\r
987 self._firstImageNumber = ( self._firstImageNumber - direction ) % imageListLength;
\r
988 if ( self._firstImageNumber < 0 ) {
\r
989 self._firstImageNumber = imageListLength - 1;
\r
992 self._lastImageNumber = ( self._lastImageNumber - direction ) % imageListLength;
\r
993 if ( self._lastImageNumber < 0 ) {
\r
994 self._lastImageNumber = imageListLength - 1;
\r
1000 _drawScene: function () {
\r
1001 if ( !this._gl || !this._shaderProgram ) {
\r
1007 shaderProgram = self._shaderProgram,
\r
1008 nodes = self._nodes,
\r
1009 nodesLength = nodes.length,
\r
1012 gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
\r
1014 gl.bindBuffer( gl.ARRAY_BUFFER, self._positionBuffer );
\r
1015 gl.vertexAttribPointer( shaderProgram.vertexPositionAttr, self._positionBuffer.itemSize, gl.FLOAT, false, 0, 0 );
\r
1017 gl.bindBuffer( gl.ARRAY_BUFFER, self._textureCoordBuffer );
\r
1018 gl.vertexAttribPointer( shaderProgram.textureCoordAttr, self._textureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0 );
\r
1020 gl.bindBuffer( gl.ARRAY_BUFFER, self._normalVectorBuffer );
\r
1021 gl.vertexAttribPointer( shaderProgram.vertexNormalAttr, self._normalVectorBuffer.itemSize, gl.FLOAT, false, 0, 0 );
\r
1023 for ( i = 0; i < nodesLength; i += 1 ) {
\r
1024 if ( nodes[i].drawable ) {
\r
1025 self._drawElement( self._pMatrix, nodes[i] );
\r
1030 _drawElement: function ( perspectiveMatrix, targetNode ) {
\r
1033 shaderProgram = self._shaderProgram,
\r
1034 moveMatrix = targetNode.mvMatrix,
\r
1035 texture = targetNode.texture,
\r
1036 meshIndexBuffer = targetNode.textureBuffer,
\r
1037 meshIndexBufferItemSize = targetNode.textureBufferItemSize,
\r
1038 lightPositions = self._lightsPositionStack,
\r
1042 if ( !moveMatrix ) {
\r
1046 gl.activeTexture( gl.TEXTURE0 );
\r
1047 if ( texture && texture.loaded ) {
\r
1048 gl.bindTexture( gl.TEXTURE_2D, texture );
\r
1050 gl.uniform1i( shaderProgram.sampleUniform, 0 );
\r
1052 LightDir = vec3.create();
\r
1053 vec3.normalize( lightPositions[0], LightDir );
\r
1054 vec3.scale( LightDir, -8 );
\r
1055 gl.uniform3fv( shaderProgram.lightDirU_first, LightDir );
\r
1057 vec3.normalize( lightPositions[1], LightDir );
\r
1058 vec3.scale( LightDir, -1 );
\r
1059 gl.uniform3fv( shaderProgram.lightDirU_second, LightDir );
\r
1060 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, meshIndexBuffer );
\r
1062 gl.uniformMatrix4fv( shaderProgram.perspectiveMU, false, perspectiveMatrix );
\r
1063 gl.uniformMatrix4fv( shaderProgram.transformMU, false, moveMatrix );
\r
1065 normalMatrix = mat3.create();
\r
1066 mat4.toInverseMat3( moveMatrix, normalMatrix );
\r
1067 mat3.transpose( normalMatrix );
\r
1068 gl.uniformMatrix3fv( shaderProgram.normalMU, false, normalMatrix );
\r
1070 gl.drawElements( gl.TRIANGLES, meshIndexBufferItemSize, gl.UNSIGNED_SHORT, 0 );
\r
1072 // release buffer memory
\r
1073 gl.bindBuffer( gl.ARRAY_BUFFER, null );
\r
1074 gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
\r
1076 // release texture memory
\r
1077 gl.bindTexture( gl.TEXTURE_2D, null );
\r
1080 // ----------------------------------------------------------
\r
1082 // ----------------------------------------------------------
\r
1083 _animate: function ( easingType, duration, direction, repeatCount, startValue, _removeCount ) {
\r
1085 timeNow = $.now(),
\r
1089 easingType = easingType || "linear";
\r
1090 startValue = startValue || 0;
\r
1091 _removeCount = _removeCount || 0;
\r
1093 if ( self._sumTime >= duration ) {
\r
1094 self._setPosition( self._ANIMATION_END, direction );
\r
1099 if ( self._startTime === 0 ) {
\r
1100 self._startTime = timeNow;
\r
1102 self._sumTime = timeNow - self._startTime;
\r
1103 progress = $.easing[ easingType ]( self._sumTime / duration, self._sumTime, startValue, repeatCount + 1, duration );
\r
1104 removeCount = parseInt( Math.abs( progress ), 10 );
\r
1106 if ( _removeCount !== removeCount ) {
\r
1107 self._setPosition( self._ANIMATION_END, direction );
\r
1108 _removeCount = removeCount;
\r
1110 if ( ( repeatCount - _removeCount ) >= 0 ) {
\r
1111 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
\r
1118 self._setPosition( progress - _removeCount, direction );
\r
1121 self._animationID = window.requestAnimationFrame( function () {
\r
1122 self._animate( easingType, duration, direction, repeatCount, startValue, _removeCount );
\r
1126 _run: function ( direction, repeatCount, startValue ) {
\r
1128 repeat = repeatCount || 0,
\r
1129 duration = self._DURATION_DEFAULT * ( repeat + 1 );
\r
1131 if ( self._imageList.length <= 1 ) {
\r
1135 startValue = startValue || 0;
\r
1136 duration = ( duration >= 0 ) ? duration : 0;
\r
1138 if ( self._animationID ) {
\r
1139 self._setPosition( self._ANIMATION_END, direction );
\r
1143 self._animate( "easeOutExpo", duration, direction, repeat, startValue );
\r
1146 _reset: function () {
\r
1147 if ( !this._canvas || !this._gl ) {
\r
1156 _stop: function () {
\r
1157 if ( this._animationID ) {
\r
1158 window.cancelAnimationFrame( this._animationID );
\r
1160 this._animationID = 0;
\r
1162 this._startTime = 0;
\r
1163 this._sumTime = 0;
\r
1166 _degreeToRadian: function ( degree ) {
\r
1167 return degree * Math.PI / 180;
\r
1170 next: function () {
\r
1171 this._run( this._DIRECTION_LEFT , 0 );
\r
1174 prev: function () {
\r
1175 this._run( this._DIRECTION_RIGHT, 0 );
\r
1178 refresh: function () {
\r
1179 var view = this.element,
\r
1180 canvas = view.find( "canvas.ui-gallery3d-canvas" );
\r
1182 if ( canvas.width() !== view.width() ) {
\r
1183 canvas.width( view.width() );
\r
1186 if ( !this._animationID ) {
\r
1187 this._setPosition( 0, 0 );
\r
1191 select: function ( index ) {
\r
1192 var nodes = this._nodes,
\r
1200 if ( index && this._animationID ) {
\r
1204 for ( i in nodes ) {
\r
1205 if ( nodes[i].level === 1 ) {
\r
1206 object = this._imageList[ nodes[i].imageID ];
\r
1207 imageID = nodes[i].imageID;
\r
1216 if ( index < 0 && index >= this._imageList.length ) {
\r
1220 target = index - imageID;
\r
1221 direction = ( target > 0 ) ? this._DIRECTION_LEFT
\r
1222 : ( ( target < 0 ) ? this._DIRECTION_RIGHT : 0 );
\r
1223 if ( direction ) {
\r
1224 this._run( direction, Math.abs( target ) - 1 );
\r
1228 add: function ( item, index ) {
\r
1233 if ( typeof item === "string" ) {
\r
1234 item = { "src" : item };
\r
1237 index = index || 0;
\r
1238 if ( typeof index !== "number" && index < 0
\r
1239 && index >= this._imageList.length ) {
\r
1243 this._imageList.splice( index, 0, item );
\r
1249 remove: function ( index ) {
\r
1250 index = index || 0;
\r
1251 if ( typeof index !== "number" && index < 0
\r
1252 && index >= this._imageList.length ) {
\r
1256 this._imageList.splice( index, 1 );
\r
1262 clearThumbnailCache: function () {
\r
1263 if ( !this._nodes || ( this._nodes.length <= 0 ) ) {
\r
1268 for ( i = 0; i < this._imageList.length; i += 1 ) {
\r
1269 url = this._imageList[i].src;
\r
1270 $.imageloader.removeThumbnail( url );
\r
1274 empty: function () {
\r
1275 this._imageList = [];
\r
1279 length: function () {
\r
1280 return this._imageList.length;
\r
1284 $( document ).bind( "pagecreate create", function ( e ) {
\r
1285 $( ":jqmData(role='gallery3d')" ).gallery3d();
\r
1288 } ( jQuery, document, window ) );
\r
1290 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
\r
1292 //>>excludeEnd("jqmBuildExclude");