PythonOcc/elbow: jsc3d.touch.js

File jsc3d.touch.js, 46.0 KB (added by Leon Kos, 10 years ago)

Pomožni program za tablice

Line 
1/**
2 * @preserve Copyright (c) 2011~2013 Humu <humu2009@gmail.com>
3 * This file is part of jsc3d project, which is freely distributable under the
4 * terms of the MIT license.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25
26/**
27        @namespace JSC3D
28 */
29var JSC3D = JSC3D || {};
30
31/*
32 * ! Hammer.JS - v1.0.5 - 2013-04-07
33 * http://eightmedia.github.com/hammer.js
34 *
35 * Copyright (c) 2013 Jorik Tangelder <j.tangelder@gmail.com>;
36 * Licensed under the MIT license
37 */
38
39(function(window, undefined) {
40    'use strict';
41
42/**
43 * Hammer
44 * use this to create instances
45 * @param   {HTMLElement}   element
46 * @param   {Object}        options
47 * @returns {Hammer.Instance}
48 * @constructor
49 */
50var Hammer = function(element, options) {
51    return new Hammer.Instance(element, options || {});
52};
53
54// default settings
55Hammer.defaults = {
56    // add styles and attributes to the element to prevent the browser from doing
57    // its native behavior. this doesnt prevent the scrolling, but cancels
58    // the contextmenu, tap highlighting etc
59    // set to false to disable this
60    stop_browser_behavior: {
61                // this also triggers onselectstart=false for IE
62        userSelect: 'none',
63                // this makes the element blocking in IE10 >, you could experiment with the value
64                // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
65        touchAction: 'none',
66                touchCallout: 'none',
67        contentZooming: 'none',
68        userDrag: 'none',
69        tapHighlightColor: 'rgba(0,0,0,0)'
70    }
71
72    // more settings are defined per gesture at gestures.js
73};
74
75// detect touchevents
76Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
77Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
78
79// dont use mouseevents on mobile devices
80Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
81Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && navigator.userAgent.match(Hammer.MOBILE_REGEX);
82
83// eventtypes per touchevent (start, move, end)
84// are filled by Hammer.event.determineEventTypes on setup
85Hammer.EVENT_TYPES = {};
86
87// direction defines
88Hammer.DIRECTION_DOWN = 'down';
89Hammer.DIRECTION_LEFT = 'left';
90Hammer.DIRECTION_UP = 'up';
91Hammer.DIRECTION_RIGHT = 'right';
92
93// pointer type
94Hammer.POINTER_MOUSE = 'mouse';
95Hammer.POINTER_TOUCH = 'touch';
96Hammer.POINTER_PEN = 'pen';
97
98// touch event defines
99Hammer.EVENT_START = 'start';
100Hammer.EVENT_MOVE = 'move';
101Hammer.EVENT_END = 'end';
102
103// hammer document where the base events are added at
104Hammer.DOCUMENT = document;
105
106// plugins namespace
107Hammer.plugins = {};
108
109// if the window events are set...
110Hammer.READY = false;
111
112/**
113 * setup events to detect gestures on the document
114 */
115function setup() {
116    if(Hammer.READY) {
117        return;
118    }
119
120    // find what eventtypes we add listeners to
121    Hammer.event.determineEventTypes();
122
123    // Register all gestures inside Hammer.gestures
124    for(var name in Hammer.gestures) {
125        if(Hammer.gestures.hasOwnProperty(name)) {
126            Hammer.detection.register(Hammer.gestures[name]);
127        }
128    }
129
130    // Add touch events on the document
131    Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_MOVE, Hammer.detection.detect);
132    Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_END, Hammer.detection.detect);
133
134    // Hammer is ready...!
135    Hammer.READY = true;
136}
137
138/**
139 * create new hammer instance
140 * all methods should return the instance itself, so it is chainable.
141 * @param   {HTMLElement}       element
142 * @param   {Object}            [options={}]
143 * @returns {Hammer.Instance}
144 * @constructor
145 */
146Hammer.Instance = function(element, options) {
147    var self = this;
148
149    // setup HammerJS window events and register all gestures
150    // this also sets up the default options
151    setup();
152
153    this.element = element;
154
155    // start/stop detection option
156    this.enabled = true;
157
158    // merge options
159    this.options = Hammer.utils.extend(
160        Hammer.utils.extend({}, Hammer.defaults),
161        options || {});
162
163    // add some css to the element to prevent the browser from doing its native behavoir
164    if(this.options.stop_browser_behavior) {
165        Hammer.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
166    }
167
168    // start detection on touchstart
169    Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) {
170        if(self.enabled) {
171            Hammer.detection.startDetect(self, ev);
172        }
173    });
174
175    // return instance
176    return this;
177};
178
179
180Hammer.Instance.prototype = {
181    /**
182     * bind events to the instance
183     * @param   {String}      gesture
184     * @param   {Function}    handler
185     * @returns {Hammer.Instance}
186     */
187    on: function onEvent(gesture, handler){
188        var gestures = gesture.split(' ');
189        for(var t=0; t<gestures.length; t++) {
190            this.element.addEventListener(gestures[t], handler, false);
191        }
192        return this;
193    },
194
195
196    /**
197     * unbind events to the instance
198     * @param   {String}      gesture
199     * @param   {Function}    handler
200     * @returns {Hammer.Instance}
201     */
202    off: function offEvent(gesture, handler){
203        var gestures = gesture.split(' ');
204        for(var t=0; t<gestures.length; t++) {
205            this.element.removeEventListener(gestures[t], handler, false);
206        }
207        return this;
208    },
209
210
211    /**
212     * trigger gesture event
213     * @param   {String}      gesture
214     * @param   {Object}      eventData
215     * @returns {Hammer.Instance}
216     */
217    trigger: function triggerEvent(gesture, eventData){
218        // create DOM event
219        var event = Hammer.DOCUMENT.createEvent('Event');
220                event.initEvent(gesture, true, true);
221                event.gesture = eventData;
222
223        // trigger on the target if it is in the instance element,
224        // this is for event delegation tricks
225        var element = this.element;
226        if(Hammer.utils.hasParent(eventData.target, element)) {
227            element = eventData.target;
228        }
229
230        element.dispatchEvent(event);
231        return this;
232    },
233
234
235    /**
236     * enable of disable hammer.js detection
237     * @param   {Boolean}   state
238     * @returns {Hammer.Instance}
239     */
240    enable: function enable(state) {
241        this.enabled = state;
242        return this;
243    }
244};
245
246/**
247 * this holds the last move event,
248 * used to fix empty touchend issue
249 * see the onTouch event for an explanation
250 * @type {Object}
251 */
252var last_move_event = null;
253
254
255/**
256 * when the mouse is hold down, this is true
257 * @type {Boolean}
258 */
259var enable_detect = false;
260
261
262/**
263 * when touch events have been fired, this is true
264 * @type {Boolean}
265 */
266var touch_triggered = false;
267
268
269Hammer.event = {
270    /**
271     * simple addEventListener
272     * @param   {HTMLElement}   element
273     * @param   {String}        type
274     * @param   {Function}      handler
275     */
276    bindDom: function(element, type, handler) {
277        var types = type.split(' ');
278        for(var t=0; t<types.length; t++) {
279            element.addEventListener(types[t], handler, false);
280        }
281    },
282
283
284    /**
285     * touch events with mouse fallback
286     * @param   {HTMLElement}   element
287     * @param   {String}        eventType        like Hammer.EVENT_MOVE
288     * @param   {Function}      handler
289     */
290    onTouch: function onTouch(element, eventType, handler) {
291                var self = this;
292
293        this.bindDom(element, Hammer.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
294            var sourceEventType = ev.type.toLowerCase();
295
296            // onmouseup, but when touchend has been fired we do nothing.
297            // this is for touchdevices which also fire a mouseup on touchend
298            if(sourceEventType.match(/mouse/) && touch_triggered) {
299                return;
300            }
301
302            // mousebutton must be down or a touch event
303            else if( sourceEventType.match(/touch/) ||   // touch events are always on screen
304                sourceEventType.match(/pointerdown/) || // pointerevents touch
305                (sourceEventType.match(/mouse/) && ev.which === 1)   // mouse is pressed
306            ){
307                enable_detect = true;
308            }
309
310            // we are in a touch event, set the touch triggered bool to true,
311            // this for the conflicts that may occur on ios and android
312            if(sourceEventType.match(/touch|pointer/)) {
313                touch_triggered = true;
314            }
315
316            // count the total touches on the screen
317            var count_touches = 0;
318
319            // when touch has been triggered in this detection session
320            // and we are now handling a mouse event, we stop that to prevent conflicts
321            if(enable_detect) {
322                // update pointerevent
323                if(Hammer.HAS_POINTEREVENTS && eventType != Hammer.EVENT_END) {
324                    count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
325                }
326                // touch
327                else if(sourceEventType.match(/touch/)) {
328                    count_touches = ev.touches.length;
329                }
330                // mouse
331                else if(!touch_triggered) {
332                    count_touches = sourceEventType.match(/up/) ? 0 : 1;
333                }
334
335                // if we are in a end event, but when we remove one touch and
336                // we still have enough, set eventType to move
337                if(count_touches > 0 && eventType == Hammer.EVENT_END) {
338                    eventType = Hammer.EVENT_MOVE;
339                }
340                // no touches, force the end event
341                else if(!count_touches) {
342                    eventType = Hammer.EVENT_END;
343                }
344
345                // because touchend has no touches, and we often want to use these in our gestures,
346                // we send the last move event as our eventData in touchend
347                if(!count_touches && last_move_event !== null) {
348                    ev = last_move_event;
349                }
350                // store the last move event
351                else {
352                    last_move_event = ev;
353                }
354
355                // trigger the handler
356                handler.call(Hammer.detection, self.collectEventData(element, eventType, ev));
357
358                // remove pointerevent from list
359                if(Hammer.HAS_POINTEREVENTS && eventType == Hammer.EVENT_END) {
360                    count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
361                }
362            }
363
364            //debug(sourceEventType +" "+ eventType);
365
366            // on the end we reset everything
367            if(!count_touches) {
368                last_move_event = null;
369                enable_detect = false;
370                touch_triggered = false;
371                Hammer.PointerEvent.reset();
372            }
373        });
374    },
375
376
377    /**
378     * we have different events for each device/browser
379     * determine what we need and set them in the Hammer.EVENT_TYPES constant
380     */
381    determineEventTypes: function determineEventTypes() {
382        // determine the eventtype we want to set
383        var types;
384
385        // pointerEvents magic
386        if(Hammer.HAS_POINTEREVENTS) {
387            types = Hammer.PointerEvent.getEvents();
388        }
389        // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
390        else if(Hammer.NO_MOUSEEVENTS) {
391            types = [
392                'touchstart',
393                'touchmove',
394                'touchend touchcancel'];
395        }
396        // for non pointer events browsers and mixed browsers,
397        // like chrome on windows8 touch laptop
398        else {
399            types = [
400                'touchstart mousedown',
401                'touchmove mousemove',
402                'touchend touchcancel mouseup'];
403        }
404
405        Hammer.EVENT_TYPES[Hammer.EVENT_START]  = types[0];
406        Hammer.EVENT_TYPES[Hammer.EVENT_MOVE]   = types[1];
407        Hammer.EVENT_TYPES[Hammer.EVENT_END]    = types[2];
408    },
409
410
411    /**
412     * create touchlist depending on the event
413     * @param   {Object}    ev
414     * @param   {String}    eventType   used by the fakemultitouch plugin
415     */
416    getTouchList: function getTouchList(ev/*, eventType*/) {
417        // get the fake pointerEvent touchlist
418        if(Hammer.HAS_POINTEREVENTS) {
419            return Hammer.PointerEvent.getTouchList();
420        }
421        // get the touchlist
422        else if(ev.touches) {
423            return ev.touches;
424        }
425        // make fake touchlist from mouse position
426        else {
427            return [{
428                identifier: 1,
429                pageX: ev.pageX,
430                pageY: ev.pageY,
431                target: ev.target
432            }];
433        }
434    },
435
436
437    /**
438     * collect event data for Hammer js
439     * @param   {HTMLElement}   element
440     * @param   {String}        eventType        like Hammer.EVENT_MOVE
441     * @param   {Object}        eventData
442     */
443    collectEventData: function collectEventData(element, eventType, ev) {
444        var touches = this.getTouchList(ev, eventType);
445
446        // find out pointerType
447        var pointerType = Hammer.POINTER_TOUCH;
448        if(ev.type.match(/mouse/) || Hammer.PointerEvent.matchType(Hammer.POINTER_MOUSE, ev)) {
449            pointerType = Hammer.POINTER_MOUSE;
450        }
451
452        return {
453            center      : Hammer.utils.getCenter(touches),
454            timeStamp   : new Date().getTime(),
455            target      : ev.target,
456            touches     : touches,
457            eventType   : eventType,
458            pointerType : pointerType,
459            srcEvent    : ev,
460
461            /**
462             * prevent the browser default actions
463             * mostly used to disable scrolling of the browser
464             */
465            preventDefault: function() {
466                if(this.srcEvent.preventManipulation) {
467                    this.srcEvent.preventManipulation();
468                }
469
470                if(this.srcEvent.preventDefault) {
471                    this.srcEvent.preventDefault();
472                }
473            },
474
475            /**
476             * stop bubbling the event up to its parents
477             */
478            stopPropagation: function() {
479                this.srcEvent.stopPropagation();
480            },
481
482            /**
483             * immediately stop gesture detection
484             * might be useful after a swipe was detected
485             * @return {*}
486             */
487            stopDetect: function() {
488                return Hammer.detection.stopDetect();
489            }
490        };
491    }
492};
493
494Hammer.PointerEvent = {
495    /**
496     * holds all pointers
497     * @type {Object}
498     */
499    pointers: {},
500
501    /**
502     * get a list of pointers
503     * @returns {Array}     touchlist
504     */
505    getTouchList: function() {
506        var self = this;
507        var touchlist = [];
508
509        // we can use forEach since pointerEvents only is in IE10
510        Object.keys(self.pointers).sort().forEach(function(id) {
511            touchlist.push(self.pointers[id]);
512        });
513        return touchlist;
514    },
515
516    /**
517     * update the position of a pointer
518     * @param   {String}   type             Hammer.EVENT_END
519     * @param   {Object}   pointerEvent
520     */
521    updatePointer: function(type, pointerEvent) {
522        if(type == Hammer.EVENT_END) {
523            this.pointers = {};
524        }
525        else {
526            pointerEvent.identifier = pointerEvent.pointerId;
527            this.pointers[pointerEvent.pointerId] = pointerEvent;
528        }
529
530        return Object.keys(this.pointers).length;
531    },
532
533    /**
534     * check if ev matches pointertype
535     * @param   {String}        pointerType     Hammer.POINTER_MOUSE
536     * @param   {PointerEvent}  ev
537     */
538    matchType: function(pointerType, ev) {
539        if(!ev.pointerType) {
540            return false;
541        }
542
543        var types = {};
544        types[Hammer.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == Hammer.POINTER_MOUSE);
545        types[Hammer.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == Hammer.POINTER_TOUCH);
546        types[Hammer.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == Hammer.POINTER_PEN);
547        return types[pointerType];
548    },
549
550
551    /**
552     * get events
553     */
554    getEvents: function() {
555        return [
556            'pointerdown MSPointerDown',
557            'pointermove MSPointerMove',
558            'pointerup pointercancel MSPointerUp MSPointerCancel'
559        ];
560    },
561
562    /**
563     * reset the list
564     */
565    reset: function() {
566        this.pointers = {};
567    }
568};
569
570
571Hammer.utils = {
572    /**
573     * extend method,
574     * also used for cloning when dest is an empty object
575     * @param   {Object}    dest
576     * @param   {Object}    src
577         * @parm        {Boolean}       merge           do a merge
578     * @returns {Object}    dest
579     */
580    extend: function extend(dest, src, merge) {
581        for (var key in src) {
582                        if(dest[key] !== undefined && merge) {
583                                continue;
584                        }
585            dest[key] = src[key];
586        }
587        return dest;
588    },
589
590
591    /**
592     * find if a node is in the given parent
593     * used for event delegation tricks
594     * @param   {HTMLElement}   node
595     * @param   {HTMLElement}   parent
596     * @returns {boolean}       has_parent
597     */
598    hasParent: function(node, parent) {
599        while(node){
600            if(node == parent) {
601                return true;
602            }
603            node = node.parentNode;
604        }
605        return false;
606    },
607
608
609    /**
610     * get the center of all the touches
611     * @param   {Array}     touches
612     * @returns {Object}    center
613     */
614    getCenter: function getCenter(touches) {
615        var valuesX = [], valuesY = [];
616
617        for(var t= 0,len=touches.length; t<len; t++) {
618            valuesX.push(touches[t].pageX);
619            valuesY.push(touches[t].pageY);
620        }
621
622        return {
623            pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
624            pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
625        };
626    },
627
628
629    /**
630     * calculate the velocity between two points
631     * @param   {Number}    delta_time
632     * @param   {Number}    delta_x
633     * @param   {Number}    delta_y
634     * @returns {Object}    velocity
635     */
636    getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
637        return {
638            x: Math.abs(delta_x / delta_time) || 0,
639            y: Math.abs(delta_y / delta_time) || 0
640        };
641    },
642
643
644    /**
645     * calculate the angle between two coordinates
646     * @param   {Touch}     touch1
647     * @param   {Touch}     touch2
648     * @returns {Number}    angle
649     */
650    getAngle: function getAngle(touch1, touch2) {
651        var y = touch2.pageY - touch1.pageY,
652            x = touch2.pageX - touch1.pageX;
653        return Math.atan2(y, x) * 180 / Math.PI;
654    },
655
656
657    /**
658     * angle to direction define
659     * @param   {Touch}     touch1
660     * @param   {Touch}     touch2
661     * @returns {String}    direction constant, like Hammer.DIRECTION_LEFT
662     */
663    getDirection: function getDirection(touch1, touch2) {
664        var x = Math.abs(touch1.pageX - touch2.pageX),
665            y = Math.abs(touch1.pageY - touch2.pageY);
666
667        if(x >= y) {
668            return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
669        }
670        else {
671            return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
672        }
673    },
674
675
676    /**
677     * calculate the distance between two touches
678     * @param   {Touch}     touch1
679     * @param   {Touch}     touch2
680     * @returns {Number}    distance
681     */
682    getDistance: function getDistance(touch1, touch2) {
683        var x = touch2.pageX - touch1.pageX,
684            y = touch2.pageY - touch1.pageY;
685        return Math.sqrt((x*x) + (y*y));
686    },
687
688
689    /**
690     * calculate the scale factor between two touchLists (fingers)
691     * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
692     * @param   {Array}     start
693     * @param   {Array}     end
694     * @returns {Number}    scale
695     */
696    getScale: function getScale(start, end) {
697        // need two fingers...
698        if(start.length >= 2 && end.length >= 2) {
699            return this.getDistance(end[0], end[1]) /
700                this.getDistance(start[0], start[1]);
701        }
702        return 1;
703    },
704
705
706    /**
707     * calculate the rotation degrees between two touchLists (fingers)
708     * @param   {Array}     start
709     * @param   {Array}     end
710     * @returns {Number}    rotation
711     */
712    getRotation: function getRotation(start, end) {
713        // need two fingers
714        if(start.length >= 2 && end.length >= 2) {
715            return this.getAngle(end[1], end[0]) -
716                this.getAngle(start[1], start[0]);
717        }
718        return 0;
719    },
720
721
722    /**
723     * boolean if the direction is vertical
724     * @param    {String}    direction
725     * @returns  {Boolean}   is_vertical
726     */
727    isVertical: function isVertical(direction) {
728        return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN);
729    },
730
731
732    /**
733     * stop browser default behavior with css props
734     * @param   {HtmlElement}   element
735     * @param   {Object}        css_props
736     */
737    stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) {
738        var prop,
739            vendors = ['webkit','khtml','moz','ms','o',''];
740
741        if(!css_props || !element.style) {
742            return;
743        }
744
745        // with css properties for modern browsers
746        for(var i = 0; i < vendors.length; i++) {
747            for(var p in css_props) {
748                if(css_props.hasOwnProperty(p)) {
749                    prop = p;
750
751                    // vender prefix at the property
752                    if(vendors[i]) {
753                        prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
754                    }
755
756                    // set the style
757                    element.style[prop] = css_props[p];
758                }
759            }
760        }
761
762        // also the disable onselectstart
763        if(css_props.userSelect == 'none') {
764            element.onselectstart = function() {
765                return false;
766            };
767        }
768    }
769};
770
771Hammer.detection = {
772    // contains all registred Hammer.gestures in the correct order
773    gestures: [],
774
775    // data of the current Hammer.gesture detection session
776    current: null,
777
778    // the previous Hammer.gesture session data
779    // is a full clone of the previous gesture.current object
780    previous: null,
781
782    // when this becomes true, no gestures are fired
783    stopped: false,
784
785
786    /**
787     * start Hammer.gesture detection
788     * @param   {Hammer.Instance}   inst
789     * @param   {Object}            eventData
790     */
791    startDetect: function startDetect(inst, eventData) {
792        // already busy with a Hammer.gesture detection on an element
793        if(this.current) {
794            return;
795        }
796
797        this.stopped = false;
798
799        this.current = {
800            inst        : inst, // reference to HammerInstance we're working for
801            startEvent  : Hammer.utils.extend({}, eventData), // start eventData for distances, timing etc
802            lastEvent   : false, // last eventData
803            name        : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
804        };
805
806        this.detect(eventData);
807    },
808
809
810    /**
811     * Hammer.gesture detection
812     * @param   {Object}    eventData
813     * @param   {Object}    eventData
814     */
815    detect: function detect(eventData) {
816        if(!this.current || this.stopped) {
817            return;
818        }
819
820        // extend event data with calculations about scale, distance etc
821        eventData = this.extendEventData(eventData);
822
823        // instance options
824        var inst_options = this.current.inst.options;
825
826        // call Hammer.gesture handlers
827        for(var g=0,len=this.gestures.length; g<len; g++) {
828            var gesture = this.gestures[g];
829
830            // only when the instance options have enabled this gesture
831            if(!this.stopped && inst_options[gesture.name] !== false) {
832                // if a handler returns false, we stop with the detection
833                if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
834                    this.stopDetect();
835                    break;
836                }
837            }
838        }
839
840        // store as previous event event
841        if(this.current) {
842            this.current.lastEvent = eventData;
843        }
844
845        // endevent, but not the last touch, so dont stop
846        if(eventData.eventType == Hammer.EVENT_END && !eventData.touches.length-1) {
847            this.stopDetect();
848        }
849
850        return eventData;
851    },
852
853
854    /**
855     * clear the Hammer.gesture vars
856     * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
857     * to stop other Hammer.gestures from being fired
858     */
859    stopDetect: function stopDetect() {
860        // clone current data to the store as the previous gesture
861        // used for the double tap gesture, since this is an other gesture detect session
862        this.previous = Hammer.utils.extend({}, this.current);
863
864        // reset the current
865        this.current = null;
866
867        // stopped!
868        this.stopped = true;
869    },
870
871
872    /**
873     * extend eventData for Hammer.gestures
874     * @param   {Object}   ev
875     * @returns {Object}   ev
876     */
877    extendEventData: function extendEventData(ev) {
878        var startEv = this.current.startEvent;
879
880        // if the touches change, set the new touches over the startEvent touches
881        // this because touchevents don't have all the touches on touchstart, or the
882        // user must place his fingers at the EXACT same time on the screen, which is not realistic
883        // but, sometimes it happens that both fingers are touching at the EXACT same time
884        if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
885            // extend 1 level deep to get the touchlist with the touch objects
886            startEv.touches = [];
887            for(var i=0,len=ev.touches.length; i<len; i++) {
888                startEv.touches.push(Hammer.utils.extend({}, ev.touches[i]));
889            }
890        }
891
892        var delta_time = ev.timeStamp - startEv.timeStamp,
893            delta_x = ev.center.pageX - startEv.center.pageX,
894            delta_y = ev.center.pageY - startEv.center.pageY,
895            velocity = Hammer.utils.getVelocity(delta_time, delta_x, delta_y);
896
897        Hammer.utils.extend(ev, {
898            deltaTime   : delta_time,
899
900            deltaX      : delta_x,
901            deltaY      : delta_y,
902
903            velocityX   : velocity.x,
904            velocityY   : velocity.y,
905
906            distance    : Hammer.utils.getDistance(startEv.center, ev.center),
907            angle       : Hammer.utils.getAngle(startEv.center, ev.center),
908            direction   : Hammer.utils.getDirection(startEv.center, ev.center),
909
910            scale       : Hammer.utils.getScale(startEv.touches, ev.touches),
911            rotation    : Hammer.utils.getRotation(startEv.touches, ev.touches),
912
913            startEvent  : startEv
914        });
915
916        return ev;
917    },
918
919
920    /**
921     * register new gesture
922     * @param   {Object}    gesture object, see gestures.js for documentation
923     * @returns {Array}     gestures
924     */
925    register: function register(gesture) {
926        // add an enable gesture options if there is no given
927        var options = gesture.defaults || {};
928        if(options[gesture.name] === undefined) {
929            options[gesture.name] = true;
930        }
931
932        // extend Hammer default options with the Hammer.gesture options
933        Hammer.utils.extend(Hammer.defaults, options, true);
934
935        // set its index
936        gesture.index = gesture.index || 1000;
937
938        // add Hammer.gesture to the list
939        this.gestures.push(gesture);
940
941        // sort the list by index
942        this.gestures.sort(function(a, b) {
943            if (a.index < b.index) {
944                return -1;
945            }
946            if (a.index > b.index) {
947                return 1;
948            }
949            return 0;
950        });
951
952        return this.gestures;
953    }
954};
955
956
957Hammer.gestures = Hammer.gestures || {};
958
959/**
960 * Custom gestures
961 * ==============================
962 *
963 * Gesture object
964 * --------------------
965 * The object structure of a gesture:
966 *
967 * { name: 'mygesture',
968 *   index: 1337,
969 *   defaults: {
970 *     mygesture_option: true
971 *   }
972 *   handler: function(type, ev, inst) {
973 *     // trigger gesture event
974 *     inst.trigger(this.name, ev);
975 *   }
976 * }
977
978 * @param   {String}    name
979 * this should be the name of the gesture, lowercase
980 * it is also being used to disable/enable the gesture per instance config.
981 *
982 * @param   {Number}    [index=1000]
983 * the index of the gesture, where it is going to be in the stack of gestures detection
984 * like when you build an gesture that depends on the drag gesture, it is a good
985 * idea to place it after the index of the drag gesture.
986 *
987 * @param   {Object}    [defaults={}]
988 * the default settings of the gesture. these are added to the instance settings,
989 * and can be overruled per instance. you can also add the name of the gesture,
990 * but this is also added by default (and set to true).
991 *
992 * @param   {Function}  handler
993 * this handles the gesture detection of your custom gesture and receives the
994 * following arguments:
995 *
996 *      @param  {Object}    eventData
997 *      event data containing the following properties:
998 *          timeStamp   {Number}        time the event occurred
999 *          target      {HTMLElement}   target element
1000 *          touches     {Array}         touches (fingers, pointers, mouse) on the screen
1001 *          pointerType {String}        kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH
1002 *          center      {Object}        center position of the touches. contains pageX and pageY
1003 *          deltaTime   {Number}        the total time of the touches in the screen
1004 *          deltaX      {Number}        the delta on x axis we haved moved
1005 *          deltaY      {Number}        the delta on y axis we haved moved
1006 *          velocityX   {Number}        the velocity on the x
1007 *          velocityY   {Number}        the velocity on y
1008 *          angle       {Number}        the angle we are moving
1009 *          direction   {String}        the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT
1010 *          distance    {Number}        the distance we haved moved
1011 *          scale       {Number}        scaling of the touches, needs 2 touches
1012 *          rotation    {Number}        rotation of the touches, needs 2 touches *
1013 *          eventType   {String}        matches Hammer.EVENT_START|MOVE|END
1014 *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *
1015 *          startEvent  {Object}        contains the same properties as above,
1016 *                                      but from the first touch. this is used to calculate
1017 *                                      distances, deltaTime, scaling etc
1018 *
1019 *      @param  {Hammer.Instance}    inst
1020 *      the instance we are doing the detection for. you can get the options from
1021 *      the inst.options object and trigger the gesture event by calling inst.trigger
1022 *
1023 *
1024 * Handle gestures
1025 * --------------------
1026 * inside the handler you can get/set Hammer.detection.current. This is the current
1027 * detection session. It has the following properties
1028 *      @param  {String}    name
1029 *      contains the name of the gesture we have detected. it has not a real function,
1030 *      only to check in other gestures if something is detected.
1031 *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can
1032 *      check if the current gesture is 'drag' by accessing Hammer.detection.current.name
1033 *
1034 *      @readonly
1035 *      @param  {Hammer.Instance}    inst
1036 *      the instance we do the detection for
1037 *
1038 *      @readonly
1039 *      @param  {Object}    startEvent
1040 *      contains the properties of the first gesture detection in this session.
1041 *      Used for calculations about timing, distance, etc.
1042 *
1043 *      @readonly
1044 *      @param  {Object}    lastEvent
1045 *      contains all the properties of the last gesture detect in this session.
1046 *
1047 * after the gesture detection session has been completed (user has released the screen)
1048 * the Hammer.detection.current object is copied into Hammer.detection.previous,
1049 * this is usefull for gestures like doubletap, where you need to know if the
1050 * previous gesture was a tap
1051 *
1052 * options that have been set by the instance can be received by calling inst.options
1053 *
1054 * You can trigger a gesture event by calling inst.trigger("mygesture", event).
1055 * The first param is the name of your gesture, the second the event argument
1056 *
1057 *
1058 * Register gestures
1059 * --------------------
1060 * When an gesture is added to the Hammer.gestures object, it is auto registered
1061 * at the setup of the first Hammer instance. You can also call Hammer.detection.register
1062 * manually and pass your gesture object as a param
1063 *
1064 */
1065
1066/**
1067 * Hold
1068 * Touch stays at the same place for x time
1069 * @events  hold
1070 */
1071Hammer.gestures.Hold = {
1072    name: 'hold',
1073    index: 10,
1074    defaults: {
1075        hold_timeout    : 500,
1076        hold_threshold  : 1
1077    },
1078    timer: null,
1079    handler: function holdGesture(ev, inst) {
1080        switch(ev.eventType) {
1081            case Hammer.EVENT_START:
1082                // clear any running timers
1083                clearTimeout(this.timer);
1084
1085                // set the gesture so we can check in the timeout if it still is
1086                Hammer.detection.current.name = this.name;
1087
1088                // set timer and if after the timeout it still is hold,
1089                // we trigger the hold event
1090                this.timer = setTimeout(function() {
1091                    if(Hammer.detection.current.name == 'hold') {
1092                        inst.trigger('hold', ev);
1093                    }
1094                }, inst.options.hold_timeout);
1095                break;
1096
1097            // when you move or end we clear the timer
1098            case Hammer.EVENT_MOVE:
1099                if(ev.distance > inst.options.hold_threshold) {
1100                    clearTimeout(this.timer);
1101                }
1102                break;
1103
1104            case Hammer.EVENT_END:
1105                clearTimeout(this.timer);
1106                break;
1107        }
1108    }
1109};
1110
1111
1112/**
1113 * Tap/DoubleTap
1114 * Quick touch at a place or double at the same place
1115 * @events  tap, doubletap
1116 */
1117Hammer.gestures.Tap = {
1118    name: 'tap',
1119    index: 100,
1120    defaults: {
1121        tap_max_touchtime       : 250,
1122        tap_max_distance        : 10,
1123                tap_always                      : true,
1124        doubletap_distance      : 20,
1125        doubletap_interval      : 300
1126    },
1127    handler: function tapGesture(ev, inst) {
1128        if(ev.eventType == Hammer.EVENT_END) {
1129            // previous gesture, for the double tap since these are two different gesture detections
1130            var prev = Hammer.detection.previous,
1131                                did_doubletap = false;
1132
1133            // when the touchtime is higher then the max touch time
1134            // or when the moving distance is too much
1135            if(ev.deltaTime > inst.options.tap_max_touchtime ||
1136                ev.distance > inst.options.tap_max_distance) {
1137                return;
1138            }
1139
1140            // check if double tap
1141            if(prev && prev.name == 'tap' &&
1142                (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
1143                ev.distance < inst.options.doubletap_distance) {
1144                                inst.trigger('doubletap', ev);
1145                                did_doubletap = true;
1146            }
1147
1148                        // do a single tap
1149                        if(!did_doubletap || inst.options.tap_always) {
1150                                Hammer.detection.current.name = 'tap';
1151                                inst.trigger(Hammer.detection.current.name, ev);
1152                        }
1153        }
1154    }
1155};
1156
1157
1158/**
1159 * Swipe
1160 * triggers swipe events when the end velocity is above the threshold
1161 * @events  swipe, swipeleft, swiperight, swipeup, swipedown
1162 */
1163Hammer.gestures.Swipe = {
1164    name: 'swipe',
1165    index: 40,
1166    defaults: {
1167        // set 0 for unlimited, but this can conflict with transform
1168        swipe_max_touches  : 1,
1169        swipe_velocity     : 0.7
1170    },
1171    handler: function swipeGesture(ev, inst) {
1172        if(ev.eventType == Hammer.EVENT_END) {
1173            // max touches
1174            if(inst.options.swipe_max_touches > 0 &&
1175                ev.touches.length > inst.options.swipe_max_touches) {
1176                return;
1177            }
1178
1179            // when the distance we moved is too small we skip this gesture
1180            // or we can be already in dragging
1181            if(ev.velocityX > inst.options.swipe_velocity ||
1182                ev.velocityY > inst.options.swipe_velocity) {
1183                // trigger swipe events
1184                inst.trigger(this.name, ev);
1185                inst.trigger(this.name + ev.direction, ev);
1186            }
1187        }
1188    }
1189};
1190
1191
1192/**
1193 * Drag
1194 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
1195 * moving left and right is a good practice. When all the drag events are blocking
1196 * you disable scrolling on that area.
1197 * @events  drag, drapleft, dragright, dragup, dragdown
1198 */
1199Hammer.gestures.Drag = {
1200    name: 'drag',
1201    index: 50,
1202    defaults: {
1203        drag_min_distance : 10,
1204        // set 0 for unlimited, but this can conflict with transform
1205        drag_max_touches  : 1,
1206        // prevent default browser behavior when dragging occurs
1207        // be careful with it, it makes the element a blocking element
1208        // when you are using the drag gesture, it is a good practice to set this true
1209        drag_block_horizontal   : false,
1210        drag_block_vertical     : false,
1211        // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
1212        // It disallows vertical directions if the initial direction was horizontal, and vice versa.
1213        drag_lock_to_axis       : false,
1214        // drag lock only kicks in when distance > drag_lock_min_distance
1215        // This way, locking occurs only when the distance has become large enough to reliably determine the direction
1216        drag_lock_min_distance : 25
1217    },
1218    triggered: false,
1219    handler: function dragGesture(ev, inst) {
1220        // current gesture isnt drag, but dragged is true
1221        // this means an other gesture is busy. now call dragend
1222        if(Hammer.detection.current.name != this.name && this.triggered) {
1223            inst.trigger(this.name +'end', ev);
1224            this.triggered = false;
1225            return;
1226        }
1227
1228        // max touches
1229        if(inst.options.drag_max_touches > 0 &&
1230            ev.touches.length > inst.options.drag_max_touches) {
1231            return;
1232        }
1233
1234        switch(ev.eventType) {
1235            case Hammer.EVENT_START:
1236                this.triggered = false;
1237                break;
1238
1239            case Hammer.EVENT_MOVE:
1240                // when the distance we moved is too small we skip this gesture
1241                // or we can be already in dragging
1242                if(ev.distance < inst.options.drag_min_distance &&
1243                    Hammer.detection.current.name != this.name) {
1244                    return;
1245                }
1246
1247                // we are dragging!
1248                Hammer.detection.current.name = this.name;
1249
1250                // lock drag to axis?
1251                if(Hammer.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) {
1252                    ev.drag_locked_to_axis = true;
1253                }
1254                var last_direction = Hammer.detection.current.lastEvent.direction;
1255                if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
1256                    // keep direction on the axis that the drag gesture started on
1257                    if(Hammer.utils.isVertical(last_direction)) {
1258                        ev.direction = (ev.deltaY < 0) ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
1259                    }
1260                    else {
1261                        ev.direction = (ev.deltaX < 0) ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
1262                    }
1263                }
1264
1265                // first time, trigger dragstart event
1266                if(!this.triggered) {
1267                    inst.trigger(this.name +'start', ev);
1268                    this.triggered = true;
1269                }
1270
1271                // trigger normal event
1272                inst.trigger(this.name, ev);
1273
1274                // direction event, like dragdown
1275                inst.trigger(this.name + ev.direction, ev);
1276
1277                // block the browser events
1278                if( (inst.options.drag_block_vertical && Hammer.utils.isVertical(ev.direction)) ||
1279                    (inst.options.drag_block_horizontal && !Hammer.utils.isVertical(ev.direction))) {
1280                    ev.preventDefault();
1281                }
1282                break;
1283
1284            case Hammer.EVENT_END:
1285                // trigger dragend
1286                if(this.triggered) {
1287                    inst.trigger(this.name +'end', ev);
1288                }
1289
1290                this.triggered = false;
1291                break;
1292        }
1293    }
1294};
1295
1296
1297/**
1298 * Transform
1299 * User want to scale or rotate with 2 fingers
1300 * @events  transform, pinch, pinchin, pinchout, rotate
1301 */
1302Hammer.gestures.Transform = {
1303    name: 'transform',
1304    index: 45,
1305    defaults: {
1306        // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
1307        transform_min_scale     : 0.01,
1308        // rotation in degrees
1309        transform_min_rotation  : 1,
1310        // prevent default browser behavior when two touches are on the screen
1311        // but it makes the element a blocking element
1312        // when you are using the transform gesture, it is a good practice to set this true
1313        transform_always_block  : false
1314    },
1315    triggered: false,
1316    handler: function transformGesture(ev, inst) {
1317        // current gesture isnt drag, but dragged is true
1318        // this means an other gesture is busy. now call dragend
1319        if(Hammer.detection.current.name != this.name && this.triggered) {
1320            inst.trigger(this.name +'end', ev);
1321            this.triggered = false;
1322            return;
1323        }
1324
1325        // atleast multitouch
1326        if(ev.touches.length < 2) {
1327            return;
1328        }
1329
1330        // prevent default when two fingers are on the screen
1331        if(inst.options.transform_always_block) {
1332            ev.preventDefault();
1333        }
1334
1335        switch(ev.eventType) {
1336            case Hammer.EVENT_START:
1337                this.triggered = false;
1338                break;
1339
1340            case Hammer.EVENT_MOVE:
1341                var scale_threshold = Math.abs(1-ev.scale);
1342                var rotation_threshold = Math.abs(ev.rotation);
1343
1344                // when the distance we moved is too small we skip this gesture
1345                // or we can be already in dragging
1346                if(scale_threshold < inst.options.transform_min_scale &&
1347                    rotation_threshold < inst.options.transform_min_rotation) {
1348                    return;
1349                }
1350
1351                // we are transforming!
1352                Hammer.detection.current.name = this.name;
1353
1354                // first time, trigger dragstart event
1355                if(!this.triggered) {
1356                    inst.trigger(this.name +'start', ev);
1357                    this.triggered = true;
1358                }
1359
1360                inst.trigger(this.name, ev); // basic transform event
1361
1362                // trigger rotate event
1363                if(rotation_threshold > inst.options.transform_min_rotation) {
1364                    inst.trigger('rotate', ev);
1365                }
1366
1367                // trigger pinch event
1368                if(scale_threshold > inst.options.transform_min_scale) {
1369                    inst.trigger('pinch', ev);
1370                    inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev);
1371                }
1372                break;
1373
1374            case Hammer.EVENT_END:
1375                // trigger dragend
1376                if(this.triggered) {
1377                    inst.trigger(this.name +'end', ev);
1378                }
1379
1380                this.triggered = false;
1381                break;
1382        }
1383    }
1384};
1385
1386
1387/**
1388 * Touch
1389 * Called as first, tells the user has touched the screen
1390 * @events  touch
1391 */
1392Hammer.gestures.Touch = {
1393    name: 'touch',
1394    index: -Infinity,
1395    defaults: {
1396        // call preventDefault at touchstart, and makes the element blocking by
1397        // disabling the scrolling of the page, but it improves gestures like
1398        // transforming and dragging.
1399        // be careful with using this, it can be very annoying for users to be stuck
1400        // on the page
1401        prevent_default: false,
1402
1403        // disable mouse events, so only touch (or pen!) input triggers events
1404        prevent_mouseevents: false
1405    },
1406    handler: function touchGesture(ev, inst) {
1407        if(inst.options.prevent_mouseevents && ev.pointerType == Hammer.POINTER_MOUSE) {
1408            ev.stopDetect();
1409            return;
1410        }
1411
1412        if(inst.options.prevent_default) {
1413            ev.preventDefault();
1414        }
1415
1416        if(ev.eventType ==  Hammer.EVENT_START) {
1417            inst.trigger(this.name, ev);
1418        }
1419    }
1420};
1421
1422
1423/**
1424 * Release
1425 * Called as last, tells the user has released the screen
1426 * @events  release
1427 */
1428Hammer.gestures.Release = {
1429    name: 'release',
1430    index: Infinity,
1431    handler: function releaseGesture(ev, inst) {
1432        if(ev.eventType ==  Hammer.EVENT_END) {
1433            inst.trigger(this.name, ev);
1434        }
1435    }
1436};
1437
1438// node export
1439if(typeof module === 'object' && typeof module.exports === 'object'){
1440    module.exports = Hammer;
1441}
1442// just window export
1443else {
1444    window.Hammer = Hammer;
1445
1446    // requireJS module definition
1447    if(typeof window.define === 'function' && window.define.amd) {
1448        window.define('hammer', [], function() {
1449            return Hammer;
1450        });
1451    }
1452}
1453}) (JSC3D);