28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Florian Günther
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/demos/svg_surface.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lib/assets/javascripts/zui53/helper.js:
--------------------------------------------------------------------------------
1 | // # ZUI53
2 | //
3 | // Copyright (c) 2011 Florian Günther
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining
6 | // a copy of this software and associated documentation files (the
7 | // "Software"), to deal in the Software without restriction, including
8 | // without limitation the rights to use, copy, modify, merge, publish,
9 | // distribute, sublicense, and/or sell copies of the Software, and to
10 | // permit persons to whom the Software is furnished to do so, subject to
11 | // the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be
14 | // included in all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 | //
24 |
25 | function registerNS(ns)
26 | {
27 | var nsParts = ns.split(".");
28 | var root = window;
29 |
30 | for(var i=0; i
2 | class exports.Base
3 | constructor: ()->
4 | @set = null
5 | @group = null
6 | @attached = false
7 | @disabled = false
8 | # @exclusive = false
9 |
10 | disable: =>
11 | @disabled = true
12 |
13 | enable: =>
14 | @disabled = false
15 |
16 | attach: ()=>
17 | @group.attach(@) if @group
18 | @attached = true
19 |
20 | detach: ()=>
21 | @attached = false
22 | # if @exclusive
23 | # @makeUnexclusive()
24 |
25 | makeExclusive: ()=>
26 | # if @exclusive
27 | # return
28 | # @exclusive = true
29 | @set.exclusive(@) if @set
30 | @attach()
31 |
32 | makeUnexclusive: ()=>
33 | # if !@exclusive
34 | # return
35 | # @exclusive = false
36 | @set.unexclusive() if @set
37 |
38 | stopEvent: (e)=>
39 | e.preventDefault()
40 | if e.stopImmediatePropagation?
41 | e.stopImmediatePropagation()
42 | false
43 |
44 | class exports.SetGroup
45 | constructor: ()->
46 | @tools = []
47 | @current = null
48 | @beforeExclusive = null
49 |
50 | add: (tool)=>
51 | tool.group = @
52 | @tools.push(tool)
53 | tool.attach() if @tools.length == 1
54 |
55 | attach: (tool)=>
56 | @current = tool
57 | for t in @tools
58 | t.detach() if t != tool
59 |
60 | requestExclusive: (tool)=>
61 | @current.detach() if @current and @current != tool
62 | @beforeExclusive = @current
63 |
64 | requestUnexclusive: ()=>
65 | @current = @beforeExclusive
66 | @current.attach() if @current
67 |
68 | class exports.Set
69 | constructor: (@default_tool)->
70 | @groups = [ new exports.SetGroup() ]
71 |
72 | @default_tool.set = @
73 | @default_tool.attach() if @default_tool
74 |
75 | add: (tool)=>
76 | @groups[0].add(tool)
77 | tool.set = @
78 |
79 | exclusive: (tool)=>
80 | # console.log 'Make Exclusive'
81 | for g in @groups
82 | g.requestExclusive(tool)
83 |
84 | @default_tool.detach() if @default_tool != tool and @default_tool
85 |
86 | unexclusive: ()=>
87 | for g in @groups
88 | g.requestUnexclusive()
89 |
90 | @default_tool.attach() if @default_tool
91 | # console.log 'Make UN-Exclusive'
--------------------------------------------------------------------------------
/vendor/assets/javascripts/jquery.mousewheel.js:
--------------------------------------------------------------------------------
1 | /*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
2 | * Licensed under the MIT License (LICENSE.txt).
3 | *
4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY
7 | *
8 | * Version: 3.0.4
9 | *
10 | * Requires: 1.2.2+
11 | */
12 |
13 | (function($) {
14 |
15 | var types = ['DOMMouseScroll', 'mousewheel'];
16 |
17 | $.event.special.mousewheel = {
18 | setup: function() {
19 | if ( this.addEventListener ) {
20 | for ( var i=types.length; i; ) {
21 | this.addEventListener( types[--i], handler, false );
22 | }
23 | } else {
24 | this.onmousewheel = handler;
25 | }
26 | },
27 |
28 | teardown: function() {
29 | if ( this.removeEventListener ) {
30 | for ( var i=types.length; i; ) {
31 | this.removeEventListener( types[--i], handler, false );
32 | }
33 | } else {
34 | this.onmousewheel = null;
35 | }
36 | }
37 | };
38 |
39 | $.fn.extend({
40 | mousewheel: function(fn) {
41 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
42 | },
43 |
44 | unmousewheel: function(fn) {
45 | return this.unbind("mousewheel", fn);
46 | }
47 | });
48 |
49 |
50 | function handler(event) {
51 | var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
52 | event = $.event.fix(orgEvent);
53 | event.type = "mousewheel";
54 |
55 | // Old school scrollwheel delta
56 | if ( event.wheelDelta ) { delta = event.wheelDelta/120; }
57 | if ( event.detail ) { delta = -event.detail/3; }
58 |
59 | // New school multidimensional scroll (touchpads) deltas
60 | deltaY = delta;
61 |
62 | // Gecko
63 | if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
64 | deltaY = 0;
65 | deltaX = -1*delta;
66 | }
67 |
68 | // Webkit
69 | if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
70 | if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
71 |
72 | // Add event and delta to the front of the arguments
73 | args.unshift(event, delta, deltaX, deltaY);
74 |
75 | return $.event.dispatch.apply(this, args);
76 | }
77 |
78 | })(jQuery);
--------------------------------------------------------------------------------
/lib/assets/javascripts/zui53/tools/pan_tool.js.coffee:
--------------------------------------------------------------------------------
1 | #= require ./toolset
2 |
3 | namespace 'ZUI53.Tools', (exports)->
4 | class exports.Pan extends exports.Base
5 | constructor: (zui)->
6 | @vp = zui
7 | @eventDispatcher = zui.viewport #window
8 |
9 | attach: ()=>
10 | # console.log "Attaching PAN"
11 | $('body').addClass('pan')
12 | $(@eventDispatcher).bind 'mousedown', @start
13 | $(@eventDispatcher).bind 'touchstart', @touch_start
14 |
15 | detach: ()=>
16 | # console.log "Detach PAN.."
17 | $('body').removeClass('pan')
18 |
19 | @touch_stop(null)
20 |
21 | $(@eventDispatcher).unbind 'mousedown', @start
22 | $(@eventDispatcher).unbind 'touchstart', @touch_start
23 |
24 | eventInSurface: (e)->
25 | for surface in @vp.surfaces
26 | if e.target == surface.node
27 | return true
28 | return e.target == @eventDispatcher
29 |
30 | start: (e)=>
31 | if @disabled
32 | return
33 |
34 | if e.shiftKey
35 | return;
36 |
37 | if not @eventInSurface(e)
38 | return
39 |
40 | # console.log "start panning"
41 | $('body').addClass('panning')
42 |
43 | @_start_with(e.screenX, e.screenY)
44 |
45 | $(@eventDispatcher).bind 'mousemove', @pan
46 | $(@eventDispatcher).bind 'mouseup', @stop
47 |
48 | @stopEvent(e)
49 |
50 | pan: (e)=>
51 | @_pan_with(e.screenX, e.screenY)
52 |
53 | stop: (e)=>
54 | # console.log "stop panning"
55 | $('body').removeClass('panning')
56 |
57 | $(@eventDispatcher).unbind 'mousemove', @pan
58 | $(@eventDispatcher).unbind 'mouseup', @stop
59 |
60 | $(@eventDispatcher).unbind 'touchmove', @touch_move
61 | $(@eventDispatcher).unbind 'touchend', @touch_stop
62 |
63 | @stopEvent(e)
64 |
65 | touch_start: (e)=>
66 |
67 | if not @eventInSurface(e)
68 | return
69 |
70 | # TODO: this will be fired 2 times - why?
71 | # console.log 'ZUI touch start'
72 | if @disabled
73 | return
74 |
75 | # console.log "start panning (touch)"
76 | @_start_with(e.originalEvent.touches[0].clientX, e.originalEvent.touches[0].clientY)
77 | $(@eventDispatcher).bind 'touchmove', @touch_move
78 | $(@eventDispatcher).bind 'touchend', @touch_stop
79 |
80 | touch_move: (e)=>
81 | if e.originalEvent.touches.length > 1
82 | @touch_stop()
83 | else
84 | x = e.originalEvent.touches[0].clientX
85 | y = e.originalEvent.touches[0].clientY
86 | @_pan_with(x, y)
87 | e.originalEvent.preventDefault()
88 |
89 | touch_stop: (e)=>
90 | # console.log 'ZUI touch stop'
91 | $(@eventDispatcher).unbind 'touchmove', @touch_move
92 | $(@eventDispatcher).unbind 'touchend', @touch_stop
93 |
94 | _pan_with: (x, y)=>
95 | # console.log "panning PAN Tool"
96 | dX = x - @startX
97 | dY = y - @startY
98 |
99 | @startX = x
100 | @startY = y
101 |
102 | @vp.panBy(dX, dY)
103 |
104 | _start_with: (x, y)=>
105 | @startX = x
106 | @startY = y
107 |
--------------------------------------------------------------------------------
/lib/assets/javascripts/zui53/tools/zoom_tool.js.coffee:
--------------------------------------------------------------------------------
1 | #= require ./toolset
2 |
3 | namespace 'ZUI53.Tools', (exports)->
4 | class exports.Zoom extends exports.Base
5 | constructor: (zui)->
6 | @vp = zui
7 | @eventDispatcher = zui.viewport
8 | @use_capture = true # on event handling
9 |
10 | @t1 = null
11 | @t2 = null
12 |
13 | @touch = {
14 | touches: [],
15 | touch_ids: []
16 | }
17 |
18 | attach: ()=>
19 | $(@eventDispatcher).mousewheel @zoom
20 | @eventDispatcher.addEventListener 'gesturestart', @gesture_start, @use_capture
21 |
22 | @eventDispatcher.addEventListener 'MozTouchDown', @moz_touch_down, @use_capture
23 | @eventDispatcher.addEventListener 'MozTouchUp', @moz_touch_up, @use_capture
24 |
25 | detach: ()=>
26 | $(@eventDispatcher).unmousewheel @zoom
27 | @eventDispatcher.removeEventListener 'gesturestart', @gesture_start, @use_capture
28 |
29 | @eventDispatcher.removeEventListener 'MozTouchDown', @moz_touch_down, @use_capture
30 | @eventDispatcher.removeEventListener 'MozTouchUp', @moz_touch_up, @use_capture
31 |
32 |
33 |
34 | fetch_touch: (e, value)=>
35 | if @t1 and @t1.streamId == e.streamId
36 | @t1 = value || e
37 | else
38 | @t2 = value || e
39 |
40 | @update_moz_touch()
41 |
42 | update_moz_touch: ()=>
43 | if @t1 and @t2
44 | # calc midpoint
45 | try
46 | mp = @find_midpoint( {touches: [@t1, @t2]} )
47 | catch e
48 | console.log e
49 |
50 | else if @t1 or @t2
51 | # remove
52 | console.log 'only one'
53 |
54 |
55 | create_touch_index: (streamId)=>
56 | i = @touch.touch_ids.indexOf(streamId)
57 | if i < 0
58 | i = @touch.touch_ids.length
59 | @touch.touch_ids[i] = streamId
60 |
61 | return i
62 |
63 | moz_touch_down: (e)=>
64 | if @disabled
65 | return
66 |
67 | @touch_df = null
68 |
69 | try
70 | i = @create_touch_index(e.streamId)
71 | @touch.touches[i] = e
72 |
73 | if @touch.touches.length == 2
74 | @_internal_gesture_start()
75 | @eventDispatcher.addEventListener 'MozTouchMove', @moz_touch_move, @use_capture
76 | catch e
77 | console.log e
78 |
79 | moz_touch_move: (e)=>
80 | i = @create_touch_index(e.streamId)
81 | @touch.touches[i] = e
82 |
83 | @touch_move(@touch)
84 | # @last_touch_p = @find_midpoint(@touch)
85 |
86 | d = @find_distance(@touch)
87 | if @touch_df
88 | s = @touch_df * d
89 | @gesture_move({scale: s})
90 | else
91 | @touch_df = 1/d
92 |
93 |
94 | moz_touch_up: (e)=>
95 | if @disabled
96 | return
97 |
98 | i = @touch.touch_ids.indexOf(e.streamId)
99 | if i > 0
100 | console.log "Removed: #{i}"
101 | if @touch.touches.length == 2
102 | @_internal_gesture_end()
103 | @eventDispatcher.removeEventListener 'MozTouchMove', @moz_touch_move, @use_capture
104 | # remove
105 | @touch.touches.splice(i, 1)
106 | @touch.touch_ids.splice(i, 1)
107 |
108 | zoom: (e)=>
109 | if @disabled
110 | return
111 |
112 | delta = e.originalEvent.wheelDelta || (e.originalEvent.detail * -1)
113 | f = 0.05
114 | if delta < 0
115 | f *= -1
116 |
117 | @vp.zoomBy(f, e.originalEvent.clientX, e.originalEvent.clientY)
118 |
119 | @stopEvent(e)
120 |
121 | gesture_start: (e)=>
122 | if @disabled
123 | return
124 |
125 | @_internal_gesture_start()
126 | @eventDispatcher.addEventListener 'gesturechange', @gesture_move, @use_capture
127 | @eventDispatcher.addEventListener 'gestureend', @gesture_end, @use_capture
128 | @eventDispatcher.addEventListener 'touchmove', @touch_move, @use_capture
129 | e.preventDefault()
130 |
131 | gesture_move: (e)=>
132 | # console.log "Gesture Move"
133 | if @last_touch_p
134 | @vp.zoomSet( @start_scale * e.scale, @last_touch_p.e(1), @last_touch_p.e(2))
135 |
136 | gesture_end: (e)=>
137 | @eventDispatcher.removeEventListener 'touchmove', @touch_move, @use_capture
138 | @eventDispatcher.removeEventListener 'gesturechange', @gesture_move, @use_capture
139 | @eventDispatcher.removeEventListener 'gestureend', @gesture_end, @use_capture
140 | @_internal_gesture_end()
141 |
142 | _internal_gesture_start: ()=>
143 | @makeExclusive()
144 | @last_touch_p = null
145 | @start_scale = @vp.scale
146 |
147 | _internal_gesture_end: ()=>
148 | @makeUnexclusive()
149 |
150 | touch_move: (e)=>
151 | # console.log "Touch Move: #{e.targetTouches.length}, #{e.touches.length}"
152 | if @last_touch_p
153 | new_touch_p = @find_midpoint(e)
154 | d = new_touch_p.subtract(@last_touch_p)
155 | @last_touch_p = new_touch_p
156 | @vp.panBy(d.e(1), d.e(2))
157 | else
158 | @last_touch_p = @find_midpoint(e)
159 |
160 | # Some Helper
161 |
162 | find_midpoint: (e)=>
163 | t1 = e.touches[0] #e.targetTouches[0]
164 | t2 = e.touches[1] #e.targetTouches[1]
165 | p1 = $V([t1.clientX, t1.clientY, 1])
166 | p2 = $V([t2.clientX, t2.clientY, 1])
167 |
168 | d = p2.subtract(p1).multiply(0.5)
169 | p = p1.add(d)
170 |
171 | find_distance: (e)=>
172 | t1 = e.touches[0] #e.targetTouches[0]
173 | t2 = e.touches[1] #e.targetTouches[1]
174 | p1 = $V([t1.clientX, t1.clientY, 1])
175 | p2 = $V([t2.clientX, t2.clientY, 1])
176 |
177 | # d = p2.subtract(p1) #.multiply(0.5)
178 | # d.length()
179 | p2.distanceFrom(p1)
--------------------------------------------------------------------------------
/lib/assets/javascripts/zui53/zui53.js.coffee:
--------------------------------------------------------------------------------
1 | #= require ./helper
2 | #= require ./tools/pan_tool
3 | #= require ./tools/zoom_tool
4 | #= require ./surfaces/svg_surface
5 | #= require ./surfaces/css_surface
6 | #= require ./surfaces/canvas_surface
7 |
8 | namespace 'ZUI53', (exports)->
9 | class exports.Viewport
10 | constructor: (vp)->
11 | if typeof vp == 'string'
12 | vp = document.getElementById(vp)
13 |
14 | @min_scale = null
15 | @max_scale = null
16 |
17 | @viewport = @styleViewport(vp)
18 | @surfaces = []
19 |
20 | # Offset Matrix, this should change in future, if viewport HTML-Element changes position
21 | @updateOffset()
22 |
23 | @reset()
24 | $(vp).scroll (e)=>
25 | # If the browser automatically scrolls our viewport, we translate the scroll into a pan and
26 | # reset the scroll. Otherwise MouseFocused Zooming and @clientToSurface is broken.
27 | # This happens when the user types into a contenteditable element and the carat moves outside
28 | # of the viewport.
29 | jVP = $(@viewport)
30 | @panBy( -jVP.scrollLeft(), -jVP.scrollTop() )
31 | jVP.scrollTop(0).scrollLeft(0)
32 |
33 | @toolset = new ZUI53.Tools.Set( new ZUI53.Tools.Zoom(@) )
34 |
35 | styleViewport: (vp)->
36 | $(vp).css({
37 | 'position': 'relative',
38 | 'overflow': 'hidden',
39 | 'width': '100%',
40 | 'height': '100%'
41 | })
42 | vp
43 |
44 | updateOffset: ()=>
45 | @vpOffset = $(@viewport).offset()
46 |
47 | @vpOffset.left -= (Number) $(window.document).scrollLeft()
48 | @vpOffset.top -= (Number) $(window.document).scrollTop()
49 |
50 | @vpOffM = $M([
51 | [1, 0, @vpOffset.left],
52 | [0, 1, @vpOffset.top],
53 | [0, 0, 1]
54 | ])
55 |
56 | return @vpOffM
57 |
58 | reset: ()=>
59 | @zoomPos = 0
60 | @scale = 1.0
61 | # Base Transformation Matrix for Scale/Pan and Point-Calculation
62 | @surfaceM = $M([
63 | [1, 0, 0],
64 | [0, 1, 0],
65 | [0, 0, 1]
66 | ])
67 | @updateSurface()
68 |
69 | addSurface: (surface)=>
70 | @surfaces.push surface
71 | @addLimits(surface.limits())
72 |
73 | removeSurface: (surface)=>
74 | i = @surfaces.indexOf(surface)
75 | @surfaces.splice(i, 1) if i >= 0
76 |
77 | addLimits: (limits)=>
78 | return unless limits
79 | if @min_scale || @max_scale
80 | @min_scale = Math.max(limits[0], @min_scale) if limits[0]
81 | @max_scale = Math.min(limits[1], @max_scale) if limits[1]
82 | else
83 | @min_scale = limits[0]
84 | @max_scale = limits[1]
85 | # console.log "LIMITS: #{@min_scale}, #{@max_scale}"
86 |
87 | clientToSurface: (x, y)=>
88 | v = $V([x, y, 1])
89 | sV = @surfaceM.inverse().multiply( @updateOffset().inverse().multiply(v) )
90 |
91 | layerToSurface: (x, y)=>
92 | v = $V([x, y, 1])
93 | sV = @surfaceM.inverse().multiply( v )
94 |
95 | surfaceToClient: (v)=>
96 | @updateOffset().multiply( @surfaceM.multiply(v) )
97 |
98 | surfaceToLayer: (v)=>
99 | @surfaceM.multiply(v)
100 |
101 | updateSurface: ()=>
102 | v = @getPanAndScale()
103 | for node in @surfaces
104 | node.apply(v[0], v[1], v[2])
105 |
106 | return true
107 |
108 | panBy: (x, y)=>
109 | @translateSurface(x, y)
110 | @updateSurface()
111 |
112 | zoomBy: (byF, clientX, clientY)=>
113 | newScale = @_pos_to_scale(@zoomPos + byF)
114 | @zoomSet(newScale, clientX, clientY)
115 |
116 | zoomSet: (newScale, clientX, clientY)=>
117 | newScale = @fitToLimits(newScale)
118 | @zoomPos = @_scale_to_pos(newScale)
119 | if newScale != @scale
120 | sf = @clientToSurface(clientX, clientY)
121 | scaleBy = newScale/@scale
122 |
123 | @surfaceM = @_scaleMatrix(@surfaceM, scaleBy)
124 | @scale = newScale
125 |
126 | c = @surfaceToClient(sf)
127 | dX = clientX - c.e(1)
128 | dY = clientY - c.e(2)
129 | @translateSurface(dX, dY)
130 |
131 | @updateSurface()
132 |
133 | # zoomByO: (byF, offsetX, offsetY)=>
134 | # # @zoomPos += byF
135 | # newScale = @_pos_to_scale(@zoomPos + byF)
136 | # @zoomSetO(newScale, offsetX, offsetY)
137 | #
138 | # zoomSetO: (newScale, offsetX, offsetY)=>
139 | # newScale = @fitToLimits(newScale)
140 | # @zoomPos = @_scale_to_pos(newScale)
141 | # if newScale != @scale
142 | # sf = @layerToSurface(offsetX, offsetY)
143 | # scaleBy = newScale/@scale
144 | #
145 | # @surfaceM = @_scaleMatrix(@surfaceM, scaleBy)
146 | # @scale = newScale
147 | #
148 | # c = @surfaceToLayer(sf)
149 | # dX = offsetX - c.e(1)
150 | # dY = offsetY - c.e(2)
151 | # @translateSurface(dX, dY)
152 | #
153 | # @updateSurface()
154 |
155 |
156 | fitToLimits: (s)=>
157 | # console.log "Try Scale: #{s}"
158 | if @min_scale && s < @min_scale
159 | s = @min_scale
160 | else if @max_scale && s > @max_scale
161 | s = @max_scale
162 | return s
163 |
164 | translateSurface: (x, y)=>
165 | @surfaceM = @_translateMatrix(@surfaceM, x, y)
166 |
167 | _translateMatrix: (m, x, y)->
168 | m.add( $M([
169 | [0, 0, x],
170 | [0, 0, y],
171 | [0, 0, 0]
172 | ]))
173 |
174 | _scaleMatrix: (m, s)->
175 | return m.multiply( $M([
176 | [s, 0, 0],
177 | [0, s, 0],
178 | [0, 0, 1]
179 | ]))
180 |
181 | _pos_to_scale: (pos)->
182 | Math.exp(pos)
183 |
184 | _scale_to_pos: (s)->
185 | Math.log(s)
186 |
187 | avp: ()=>
188 | @updateOffset()
189 | min = @clientToSurface(@vpOffset.left, @vpOffset.top)
190 | max = @clientToSurface(@vpOffset.left + $(@viewport).width(), @vpOffset.top + $(@viewport).height())
191 |
192 | del = max.subtract(min)
193 |
194 | return {
195 | x: min.e(1),
196 | y: min.e(2),
197 | width: del.e(1),
198 | height: del.e(2)
199 | }
200 |
201 | _boundsCenter: (b)->
202 | return {
203 | x: (b.x + b.width/2),
204 | y: (b.y + b.height/2)
205 | }
206 |
207 | showBounds: (evp)=>
208 | if evp.width == 0 or evp.height == 0
209 | return
210 |
211 | avp = @avp()
212 | s = Math.min(avp.width/evp.width, avp.height/evp.height)
213 |
214 | # Expand
215 | exp = 50/s #expand 50px, just a constant at the moment, should be variable
216 | evp.x -= exp
217 | evp.y -= exp
218 | evp.width += 2*exp
219 | evp.height += 2*exp
220 | s = Math.min(avp.width/evp.width, avp.height/evp.height)
221 |
222 | s = @fitToLimits(s)
223 | eC = @_boundsCenter(evp)
224 | aC = @_boundsCenter(avp)
225 |
226 | @setPanAndScale(-eC.x*s, -eC.y*s, s)
227 | @translateSurface( $(@viewport).width()/2, $(@viewport).height()/2) # Center
228 |
229 | @updateSurface()
230 |
231 | getPanAndScale: ()=>
232 | [@surfaceM.e(1, 3), @surfaceM.e(2, 3), @surfaceM.e(1, 1)]
233 |
234 | setPanAndScale: (panX, panY, scale)=>
235 | @surfaceM = $M([
236 | [1, 0, 0],
237 | [0, 1, 0],
238 | [0, 0, 1]
239 | ])
240 |
241 | @translateSurface(panX, panY)
242 | @surfaceM = @_scaleMatrix(@surfaceM, scale)
243 | @scale = scale
244 | @zoomPos = @_scale_to_pos(scale)
245 |
246 | getTransformString: ()=>
247 | @getPanAndScale().join(',')
248 |
249 | setTransformString: (str)=>
250 | return unless str
251 | v = str.split(',')
252 | # console.log v.length
253 | # return unless v.length == 3
254 | panX = (Number) v[0]
255 | panY = (Number) v[1]
256 | scale = (Number) v[2]
257 | @setPanAndScale(panX, panY, scale)
258 | @updateSurface()
259 |
260 |
261 |
262 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/sylvester.js:
--------------------------------------------------------------------------------
1 | // # Sylvester
2 | // Vector and Matrix mathematics modules for JavaScript
3 | // Copyright (c) 2007 James Coglan
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining
6 | // a copy of this software and associated documentation files (the "Software"),
7 | // to deal in the Software without restriction, including without limitation
8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | // and/or sell copies of the Software, and to permit persons to whom the
10 | // Software is furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included
13 | // in all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | // DEALINGS IN THE SOFTWARE.
22 |
23 | var Sylvester = {
24 | version: '0.1.3',
25 | precision: 1e-6
26 | };
27 |
28 | function Vector() {}
29 | Vector.prototype = {
30 |
31 | // Returns element i of the vector
32 | e: function(i) {
33 | return (i < 1 || i > this.elements.length) ? null : this.elements[i-1];
34 | },
35 |
36 | // Returns the number of elements the vector has
37 | dimensions: function() {
38 | return this.elements.length;
39 | },
40 |
41 | // Returns the modulus ('length') of the vector
42 | modulus: function() {
43 | return Math.sqrt(this.dot(this));
44 | },
45 |
46 | // Returns true iff the vector is equal to the argument
47 | eql: function(vector) {
48 | var n = this.elements.length;
49 | var V = vector.elements || vector;
50 | if (n != V.length) { return false; }
51 | do {
52 | if (Math.abs(this.elements[n-1] - V[n-1]) > Sylvester.precision) { return false; }
53 | } while (--n);
54 | return true;
55 | },
56 |
57 | // Returns a copy of the vector
58 | dup: function() {
59 | return Vector.create(this.elements);
60 | },
61 |
62 | // Maps the vector to another vector according to the given function
63 | map: function(fn) {
64 | var elements = [];
65 | this.each(function(x, i) {
66 | elements.push(fn(x, i));
67 | });
68 | return Vector.create(elements);
69 | },
70 |
71 | // Calls the iterator for each element of the vector in turn
72 | each: function(fn) {
73 | var n = this.elements.length, k = n, i;
74 | do { i = k - n;
75 | fn(this.elements[i], i+1);
76 | } while (--n);
77 | },
78 |
79 | // Returns a new vector created by normalizing the receiver
80 | toUnitVector: function() {
81 | var r = this.modulus();
82 | if (r === 0) { return this.dup(); }
83 | return this.map(function(x) { return x/r; });
84 | },
85 |
86 | // Returns the angle between the vector and the argument (also a vector)
87 | angleFrom: function(vector) {
88 | var V = vector.elements || vector;
89 | var n = this.elements.length, k = n, i;
90 | if (n != V.length) { return null; }
91 | var dot = 0, mod1 = 0, mod2 = 0;
92 | // Work things out in parallel to save time
93 | this.each(function(x, i) {
94 | dot += x * V[i-1];
95 | mod1 += x * x;
96 | mod2 += V[i-1] * V[i-1];
97 | });
98 | mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2);
99 | if (mod1*mod2 === 0) { return null; }
100 | var theta = dot / (mod1*mod2);
101 | if (theta < -1) { theta = -1; }
102 | if (theta > 1) { theta = 1; }
103 | return Math.acos(theta);
104 | },
105 |
106 | // Returns true iff the vector is parallel to the argument
107 | isParallelTo: function(vector) {
108 | var angle = this.angleFrom(vector);
109 | return (angle === null) ? null : (angle <= Sylvester.precision);
110 | },
111 |
112 | // Returns true iff the vector is antiparallel to the argument
113 | isAntiparallelTo: function(vector) {
114 | var angle = this.angleFrom(vector);
115 | return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
116 | },
117 |
118 | // Returns true iff the vector is perpendicular to the argument
119 | isPerpendicularTo: function(vector) {
120 | var dot = this.dot(vector);
121 | return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
122 | },
123 |
124 | // Returns the result of adding the argument to the vector
125 | add: function(vector) {
126 | var V = vector.elements || vector;
127 | if (this.elements.length != V.length) { return null; }
128 | return this.map(function(x, i) { return x + V[i-1]; });
129 | },
130 |
131 | // Returns the result of subtracting the argument from the vector
132 | subtract: function(vector) {
133 | var V = vector.elements || vector;
134 | if (this.elements.length != V.length) { return null; }
135 | return this.map(function(x, i) { return x - V[i-1]; });
136 | },
137 |
138 | // Returns the result of multiplying the elements of the vector by the argument
139 | multiply: function(k) {
140 | return this.map(function(x) { return x*k; });
141 | },
142 |
143 | x: function(k) { return this.multiply(k); },
144 |
145 | // Returns the scalar product of the vector with the argument
146 | // Both vectors must have equal dimensionality
147 | dot: function(vector) {
148 | var V = vector.elements || vector;
149 | var i, product = 0, n = this.elements.length;
150 | if (n != V.length) { return null; }
151 | do { product += this.elements[n-1] * V[n-1]; } while (--n);
152 | return product;
153 | },
154 |
155 | // Returns the vector product of the vector with the argument
156 | // Both vectors must have dimensionality 3
157 | cross: function(vector) {
158 | var B = vector.elements || vector;
159 | if (this.elements.length != 3 || B.length != 3) { return null; }
160 | var A = this.elements;
161 | return Vector.create([
162 | (A[1] * B[2]) - (A[2] * B[1]),
163 | (A[2] * B[0]) - (A[0] * B[2]),
164 | (A[0] * B[1]) - (A[1] * B[0])
165 | ]);
166 | },
167 |
168 | // Returns the (absolute) largest element of the vector
169 | max: function() {
170 | var m = 0, n = this.elements.length, k = n, i;
171 | do { i = k - n;
172 | if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; }
173 | } while (--n);
174 | return m;
175 | },
176 |
177 | // Returns the index of the first match found
178 | indexOf: function(x) {
179 | var index = null, n = this.elements.length, k = n, i;
180 | do { i = k - n;
181 | if (index === null && this.elements[i] == x) {
182 | index = i + 1;
183 | }
184 | } while (--n);
185 | return index;
186 | },
187 |
188 | // Returns a diagonal matrix with the vector's elements as its diagonal elements
189 | toDiagonalMatrix: function() {
190 | return Matrix.Diagonal(this.elements);
191 | },
192 |
193 | // Returns the result of rounding the elements of the vector
194 | round: function() {
195 | return this.map(function(x) { return Math.round(x); });
196 | },
197 |
198 | // Returns a copy of the vector with elements set to the given value if they
199 | // differ from it by less than Sylvester.precision
200 | snapTo: function(x) {
201 | return this.map(function(y) {
202 | return (Math.abs(y - x) <= Sylvester.precision) ? x : y;
203 | });
204 | },
205 |
206 | // Returns the vector's distance from the argument, when considered as a point in space
207 | distanceFrom: function(obj) {
208 | if (obj.anchor) { return obj.distanceFrom(this); }
209 | var V = obj.elements || obj;
210 | if (V.length != this.elements.length) { return null; }
211 | var sum = 0, part;
212 | this.each(function(x, i) {
213 | part = x - V[i-1];
214 | sum += part * part;
215 | });
216 | return Math.sqrt(sum);
217 | },
218 |
219 | // Returns true if the vector is point on the given line
220 | liesOn: function(line) {
221 | return line.contains(this);
222 | },
223 |
224 | // Return true iff the vector is a point in the given plane
225 | liesIn: function(plane) {
226 | return plane.contains(this);
227 | },
228 |
229 | // Rotates the vector about the given object. The object should be a
230 | // point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
231 | rotate: function(t, obj) {
232 | var V, R, x, y, z;
233 | switch (this.elements.length) {
234 | case 2:
235 | V = obj.elements || obj;
236 | if (V.length != 2) { return null; }
237 | R = Matrix.Rotation(t).elements;
238 | x = this.elements[0] - V[0];
239 | y = this.elements[1] - V[1];
240 | return Vector.create([
241 | V[0] + R[0][0] * x + R[0][1] * y,
242 | V[1] + R[1][0] * x + R[1][1] * y
243 | ]);
244 | break;
245 | case 3:
246 | if (!obj.direction) { return null; }
247 | var C = obj.pointClosestTo(this).elements;
248 | R = Matrix.Rotation(t, obj.direction).elements;
249 | x = this.elements[0] - C[0];
250 | y = this.elements[1] - C[1];
251 | z = this.elements[2] - C[2];
252 | return Vector.create([
253 | C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z,
254 | C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z,
255 | C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z
256 | ]);
257 | break;
258 | default:
259 | return null;
260 | }
261 | },
262 |
263 | // Returns the result of reflecting the point in the given point, line or plane
264 | reflectionIn: function(obj) {
265 | if (obj.anchor) {
266 | // obj is a plane or line
267 | var P = this.elements.slice();
268 | var C = obj.pointClosestTo(P).elements;
269 | return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
270 | } else {
271 | // obj is a point
272 | var Q = obj.elements || obj;
273 | if (this.elements.length != Q.length) { return null; }
274 | return this.map(function(x, i) { return Q[i-1] + (Q[i-1] - x); });
275 | }
276 | },
277 |
278 | // Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
279 | to3D: function() {
280 | var V = this.dup();
281 | switch (V.elements.length) {
282 | case 3: break;
283 | case 2: V.elements.push(0); break;
284 | default: return null;
285 | }
286 | return V;
287 | },
288 |
289 | // Returns a string representation of the vector
290 | inspect: function() {
291 | return '[' + this.elements.join(', ') + ']';
292 | },
293 |
294 | // Set vector's elements from an array
295 | setElements: function(els) {
296 | this.elements = (els.elements || els).slice();
297 | return this;
298 | }
299 | };
300 |
301 | // Constructor function
302 | Vector.create = function(elements) {
303 | var V = new Vector();
304 | return V.setElements(elements);
305 | };
306 |
307 | // i, j, k unit vectors
308 | Vector.i = Vector.create([1,0,0]);
309 | Vector.j = Vector.create([0,1,0]);
310 | Vector.k = Vector.create([0,0,1]);
311 |
312 | // Random vector of size n
313 | Vector.Random = function(n) {
314 | var elements = [];
315 | do { elements.push(Math.random());
316 | } while (--n);
317 | return Vector.create(elements);
318 | };
319 |
320 | // Vector filled with zeros
321 | Vector.Zero = function(n) {
322 | var elements = [];
323 | do { elements.push(0);
324 | } while (--n);
325 | return Vector.create(elements);
326 | };
327 |
328 |
329 |
330 | function Matrix() {}
331 | Matrix.prototype = {
332 |
333 | // Returns element (i,j) of the matrix
334 | e: function(i,j) {
335 | if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; }
336 | return this.elements[i-1][j-1];
337 | },
338 |
339 | // Returns row k of the matrix as a vector
340 | row: function(i) {
341 | if (i > this.elements.length) { return null; }
342 | return Vector.create(this.elements[i-1]);
343 | },
344 |
345 | // Returns column k of the matrix as a vector
346 | col: function(j) {
347 | if (j > this.elements[0].length) { return null; }
348 | var col = [], n = this.elements.length, k = n, i;
349 | do { i = k - n;
350 | col.push(this.elements[i][j-1]);
351 | } while (--n);
352 | return Vector.create(col);
353 | },
354 |
355 | // Returns the number of rows/columns the matrix has
356 | dimensions: function() {
357 | return {rows: this.elements.length, cols: this.elements[0].length};
358 | },
359 |
360 | // Returns the number of rows in the matrix
361 | rows: function() {
362 | return this.elements.length;
363 | },
364 |
365 | // Returns the number of columns in the matrix
366 | cols: function() {
367 | return this.elements[0].length;
368 | },
369 |
370 | // Returns true iff the matrix is equal to the argument. You can supply
371 | // a vector as the argument, in which case the receiver must be a
372 | // one-column matrix equal to the vector.
373 | eql: function(matrix) {
374 | var M = matrix.elements || matrix;
375 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
376 | if (this.elements.length != M.length ||
377 | this.elements[0].length != M[0].length) { return false; }
378 | var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
379 | do { i = ki - ni;
380 | nj = kj;
381 | do { j = kj - nj;
382 | if (Math.abs(this.elements[i][j] - M[i][j]) > Sylvester.precision) { return false; }
383 | } while (--nj);
384 | } while (--ni);
385 | return true;
386 | },
387 |
388 | // Returns a copy of the matrix
389 | dup: function() {
390 | return Matrix.create(this.elements);
391 | },
392 |
393 | // Maps the matrix to another matrix (of the same dimensions) according to the given function
394 | map: function(fn) {
395 | var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
396 | do { i = ki - ni;
397 | nj = kj;
398 | els[i] = [];
399 | do { j = kj - nj;
400 | els[i][j] = fn(this.elements[i][j], i + 1, j + 1);
401 | } while (--nj);
402 | } while (--ni);
403 | return Matrix.create(els);
404 | },
405 |
406 | // Returns true iff the argument has the same dimensions as the matrix
407 | isSameSizeAs: function(matrix) {
408 | var M = matrix.elements || matrix;
409 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
410 | return (this.elements.length == M.length &&
411 | this.elements[0].length == M[0].length);
412 | },
413 |
414 | // Returns the result of adding the argument to the matrix
415 | add: function(matrix) {
416 | var M = matrix.elements || matrix;
417 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
418 | if (!this.isSameSizeAs(M)) { return null; }
419 | return this.map(function(x, i, j) { return x + M[i-1][j-1]; });
420 | },
421 |
422 | // Returns the result of subtracting the argument from the matrix
423 | subtract: function(matrix) {
424 | var M = matrix.elements || matrix;
425 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
426 | if (!this.isSameSizeAs(M)) { return null; }
427 | return this.map(function(x, i, j) { return x - M[i-1][j-1]; });
428 | },
429 |
430 | // Returns true iff the matrix can multiply the argument from the left
431 | canMultiplyFromLeft: function(matrix) {
432 | var M = matrix.elements || matrix;
433 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
434 | // this.columns should equal matrix.rows
435 | return (this.elements[0].length == M.length);
436 | },
437 |
438 | // Returns the result of multiplying the matrix from the right by the argument.
439 | // If the argument is a scalar then just multiply all the elements. If the argument is
440 | // a vector, a vector is returned, which saves you having to remember calling
441 | // col(1) on the result.
442 | multiply: function(matrix) {
443 | if (!matrix.elements) {
444 | return this.map(function(x) { return x * matrix; });
445 | }
446 | var returnVector = matrix.modulus ? true : false;
447 | var M = matrix.elements || matrix;
448 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
449 | if (!this.canMultiplyFromLeft(M)) { return null; }
450 | var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
451 | var cols = this.elements[0].length, elements = [], sum, nc, c;
452 | do { i = ki - ni;
453 | elements[i] = [];
454 | nj = kj;
455 | do { j = kj - nj;
456 | sum = 0;
457 | nc = cols;
458 | do { c = cols - nc;
459 | sum += this.elements[i][c] * M[c][j];
460 | } while (--nc);
461 | elements[i][j] = sum;
462 | } while (--nj);
463 | } while (--ni);
464 | var M = Matrix.create(elements);
465 | return returnVector ? M.col(1) : M;
466 | },
467 |
468 | x: function(matrix) { return this.multiply(matrix); },
469 |
470 | // Returns a submatrix taken from the matrix
471 | // Argument order is: start row, start col, nrows, ncols
472 | // Element selection wraps if the required index is outside the matrix's bounds, so you could
473 | // use this to perform row/column cycling or copy-augmenting.
474 | minor: function(a, b, c, d) {
475 | var elements = [], ni = c, i, nj, j;
476 | var rows = this.elements.length, cols = this.elements[0].length;
477 | do { i = c - ni;
478 | elements[i] = [];
479 | nj = d;
480 | do { j = d - nj;
481 | elements[i][j] = this.elements[(a+i-1)%rows][(b+j-1)%cols];
482 | } while (--nj);
483 | } while (--ni);
484 | return Matrix.create(elements);
485 | },
486 |
487 | // Returns the transpose of the matrix
488 | transpose: function() {
489 | var rows = this.elements.length, cols = this.elements[0].length;
490 | var elements = [], ni = cols, i, nj, j;
491 | do { i = cols - ni;
492 | elements[i] = [];
493 | nj = rows;
494 | do { j = rows - nj;
495 | elements[i][j] = this.elements[j][i];
496 | } while (--nj);
497 | } while (--ni);
498 | return Matrix.create(elements);
499 | },
500 |
501 | // Returns true iff the matrix is square
502 | isSquare: function() {
503 | return (this.elements.length == this.elements[0].length);
504 | },
505 |
506 | // Returns the (absolute) largest element of the matrix
507 | max: function() {
508 | var m = 0, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
509 | do { i = ki - ni;
510 | nj = kj;
511 | do { j = kj - nj;
512 | if (Math.abs(this.elements[i][j]) > Math.abs(m)) { m = this.elements[i][j]; }
513 | } while (--nj);
514 | } while (--ni);
515 | return m;
516 | },
517 |
518 | // Returns the indeces of the first match found by reading row-by-row from left to right
519 | indexOf: function(x) {
520 | var index = null, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
521 | do { i = ki - ni;
522 | nj = kj;
523 | do { j = kj - nj;
524 | if (this.elements[i][j] == x) { return {i: i+1, j: j+1}; }
525 | } while (--nj);
526 | } while (--ni);
527 | return null;
528 | },
529 |
530 | // If the matrix is square, returns the diagonal elements as a vector.
531 | // Otherwise, returns null.
532 | diagonal: function() {
533 | if (!this.isSquare) { return null; }
534 | var els = [], n = this.elements.length, k = n, i;
535 | do { i = k - n;
536 | els.push(this.elements[i][i]);
537 | } while (--n);
538 | return Vector.create(els);
539 | },
540 |
541 | // Make the matrix upper (right) triangular by Gaussian elimination.
542 | // This method only adds multiples of rows to other rows. No rows are
543 | // scaled up or switched, and the determinant is preserved.
544 | toRightTriangular: function() {
545 | var M = this.dup(), els;
546 | var n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p;
547 | do { i = k - n;
548 | if (M.elements[i][i] == 0) {
549 | for (j = i + 1; j < k; j++) {
550 | if (M.elements[j][i] != 0) {
551 | els = []; np = kp;
552 | do { p = kp - np;
553 | els.push(M.elements[i][p] + M.elements[j][p]);
554 | } while (--np);
555 | M.elements[i] = els;
556 | break;
557 | }
558 | }
559 | }
560 | if (M.elements[i][i] != 0) {
561 | for (j = i + 1; j < k; j++) {
562 | var multiplier = M.elements[j][i] / M.elements[i][i];
563 | els = []; np = kp;
564 | do { p = kp - np;
565 | // Elements with column numbers up to an including the number
566 | // of the row that we're subtracting can safely be set straight to
567 | // zero, since that's the point of this routine and it avoids having
568 | // to loop over and correct rounding errors later
569 | els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier);
570 | } while (--np);
571 | M.elements[j] = els;
572 | }
573 | }
574 | } while (--n);
575 | return M;
576 | },
577 |
578 | toUpperTriangular: function() { return this.toRightTriangular(); },
579 |
580 | // Returns the determinant for square matrices
581 | determinant: function() {
582 | if (!this.isSquare()) { return null; }
583 | var M = this.toRightTriangular();
584 | var det = M.elements[0][0], n = M.elements.length - 1, k = n, i;
585 | do { i = k - n + 1;
586 | det = det * M.elements[i][i];
587 | } while (--n);
588 | return det;
589 | },
590 |
591 | det: function() { return this.determinant(); },
592 |
593 | // Returns true iff the matrix is singular
594 | isSingular: function() {
595 | return (this.isSquare() && this.determinant() === 0);
596 | },
597 |
598 | // Returns the trace for square matrices
599 | trace: function() {
600 | if (!this.isSquare()) { return null; }
601 | var tr = this.elements[0][0], n = this.elements.length - 1, k = n, i;
602 | do { i = k - n + 1;
603 | tr += this.elements[i][i];
604 | } while (--n);
605 | return tr;
606 | },
607 |
608 | tr: function() { return this.trace(); },
609 |
610 | // Returns the rank of the matrix
611 | rank: function() {
612 | var M = this.toRightTriangular(), rank = 0;
613 | var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
614 | do { i = ki - ni;
615 | nj = kj;
616 | do { j = kj - nj;
617 | if (Math.abs(M.elements[i][j]) > Sylvester.precision) { rank++; break; }
618 | } while (--nj);
619 | } while (--ni);
620 | return rank;
621 | },
622 |
623 | rk: function() { return this.rank(); },
624 |
625 | // Returns the result of attaching the given argument to the right-hand side of the matrix
626 | augment: function(matrix) {
627 | var M = matrix.elements || matrix;
628 | if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
629 | var T = this.dup(), cols = T.elements[0].length;
630 | var ni = T.elements.length, ki = ni, i, nj, kj = M[0].length, j;
631 | if (ni != M.length) { return null; }
632 | do { i = ki - ni;
633 | nj = kj;
634 | do { j = kj - nj;
635 | T.elements[i][cols + j] = M[i][j];
636 | } while (--nj);
637 | } while (--ni);
638 | return T;
639 | },
640 |
641 | // Returns the inverse (if one exists) using Gauss-Jordan
642 | inverse: function() {
643 | if (!this.isSquare() || this.isSingular()) { return null; }
644 | var ni = this.elements.length, ki = ni, i, j;
645 | var M = this.augment(Matrix.I(ni)).toRightTriangular();
646 | var np, kp = M.elements[0].length, p, els, divisor;
647 | var inverse_elements = [], new_element;
648 | // Matrix is non-singular so there will be no zeros on the diagonal
649 | // Cycle through rows from last to first
650 | do { i = ni - 1;
651 | // First, normalise diagonal elements to 1
652 | els = []; np = kp;
653 | inverse_elements[i] = [];
654 | divisor = M.elements[i][i];
655 | do { p = kp - np;
656 | new_element = M.elements[i][p] / divisor;
657 | els.push(new_element);
658 | // Shuffle of the current row of the right hand side into the results
659 | // array as it will not be modified by later runs through this loop
660 | if (p >= ki) { inverse_elements[i].push(new_element); }
661 | } while (--np);
662 | M.elements[i] = els;
663 | // Then, subtract this row from those above it to
664 | // give the identity matrix on the left hand side
665 | for (j = 0; j < i; j++) {
666 | els = []; np = kp;
667 | do { p = kp - np;
668 | els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]);
669 | } while (--np);
670 | M.elements[j] = els;
671 | }
672 | } while (--ni);
673 | return Matrix.create(inverse_elements);
674 | },
675 |
676 | inv: function() { return this.inverse(); },
677 |
678 | // Returns the result of rounding all the elements
679 | round: function() {
680 | return this.map(function(x) { return Math.round(x); });
681 | },
682 |
683 | // Returns a copy of the matrix with elements set to the given value if they
684 | // differ from it by less than Sylvester.precision
685 | snapTo: function(x) {
686 | return this.map(function(p) {
687 | return (Math.abs(p - x) <= Sylvester.precision) ? x : p;
688 | });
689 | },
690 |
691 | // Returns a string representation of the matrix
692 | inspect: function() {
693 | var matrix_rows = [];
694 | var n = this.elements.length, k = n, i;
695 | do { i = k - n;
696 | matrix_rows.push(Vector.create(this.elements[i]).inspect());
697 | } while (--n);
698 | return matrix_rows.join('\n');
699 | },
700 |
701 | // Set the matrix's elements from an array. If the argument passed
702 | // is a vector, the resulting matrix will be a single column.
703 | setElements: function(els) {
704 | var i, elements = els.elements || els;
705 | if (typeof(elements[0][0]) != 'undefined') {
706 | var ni = elements.length, ki = ni, nj, kj, j;
707 | this.elements = [];
708 | do { i = ki - ni;
709 | nj = elements[i].length; kj = nj;
710 | this.elements[i] = [];
711 | do { j = kj - nj;
712 | this.elements[i][j] = elements[i][j];
713 | } while (--nj);
714 | } while(--ni);
715 | return this;
716 | }
717 | var n = elements.length, k = n;
718 | this.elements = [];
719 | do { i = k - n;
720 | this.elements.push([elements[i]]);
721 | } while (--n);
722 | return this;
723 | }
724 | };
725 |
726 | // Constructor function
727 | Matrix.create = function(elements) {
728 | var M = new Matrix();
729 | return M.setElements(elements);
730 | };
731 |
732 | // Identity matrix of size n
733 | Matrix.I = function(n) {
734 | var els = [], k = n, i, nj, j;
735 | do { i = k - n;
736 | els[i] = []; nj = k;
737 | do { j = k - nj;
738 | els[i][j] = (i == j) ? 1 : 0;
739 | } while (--nj);
740 | } while (--n);
741 | return Matrix.create(els);
742 | };
743 |
744 | // Diagonal matrix - all off-diagonal elements are zero
745 | Matrix.Diagonal = function(elements) {
746 | var n = elements.length, k = n, i;
747 | var M = Matrix.I(n);
748 | do { i = k - n;
749 | M.elements[i][i] = elements[i];
750 | } while (--n);
751 | return M;
752 | };
753 |
754 | // Rotation matrix about some axis. If no axis is
755 | // supplied, assume we're after a 2D transform
756 | Matrix.Rotation = function(theta, a) {
757 | if (!a) {
758 | return Matrix.create([
759 | [Math.cos(theta), -Math.sin(theta)],
760 | [Math.sin(theta), Math.cos(theta)]
761 | ]);
762 | }
763 | var axis = a.dup();
764 | if (axis.elements.length != 3) { return null; }
765 | var mod = axis.modulus();
766 | var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod;
767 | var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c;
768 | // Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp
769 | // That proof rotates the co-ordinate system so theta
770 | // becomes -theta and sin becomes -sin here.
771 | return Matrix.create([
772 | [ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
773 | [ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
774 | [ t*x*z - s*y, t*y*z + s*x, t*z*z + c ]
775 | ]);
776 | };
777 |
778 | // Special case rotations
779 | Matrix.RotationX = function(t) {
780 | var c = Math.cos(t), s = Math.sin(t);
781 | return Matrix.create([
782 | [ 1, 0, 0 ],
783 | [ 0, c, -s ],
784 | [ 0, s, c ]
785 | ]);
786 | };
787 | Matrix.RotationY = function(t) {
788 | var c = Math.cos(t), s = Math.sin(t);
789 | return Matrix.create([
790 | [ c, 0, s ],
791 | [ 0, 1, 0 ],
792 | [ -s, 0, c ]
793 | ]);
794 | };
795 | Matrix.RotationZ = function(t) {
796 | var c = Math.cos(t), s = Math.sin(t);
797 | return Matrix.create([
798 | [ c, -s, 0 ],
799 | [ s, c, 0 ],
800 | [ 0, 0, 1 ]
801 | ]);
802 | };
803 |
804 | // Random matrix of n rows, m columns
805 | Matrix.Random = function(n, m) {
806 | return Matrix.Zero(n, m).map(
807 | function() { return Math.random(); }
808 | );
809 | };
810 |
811 | // Matrix filled with zeros
812 | Matrix.Zero = function(n, m) {
813 | var els = [], ni = n, i, nj, j;
814 | do { i = n - ni;
815 | els[i] = [];
816 | nj = m;
817 | do { j = m - nj;
818 | els[i][j] = 0;
819 | } while (--nj);
820 | } while (--ni);
821 | return Matrix.create(els);
822 | };
823 |
824 |
825 |
826 | function Line() {}
827 | Line.prototype = {
828 |
829 | // Returns true if the argument occupies the same space as the line
830 | eql: function(line) {
831 | return (this.isParallelTo(line) && this.contains(line.anchor));
832 | },
833 |
834 | // Returns a copy of the line
835 | dup: function() {
836 | return Line.create(this.anchor, this.direction);
837 | },
838 |
839 | // Returns the result of translating the line by the given vector/array
840 | translate: function(vector) {
841 | var V = vector.elements || vector;
842 | return Line.create([
843 | this.anchor.elements[0] + V[0],
844 | this.anchor.elements[1] + V[1],
845 | this.anchor.elements[2] + (V[2] || 0)
846 | ], this.direction);
847 | },
848 |
849 | // Returns true if the line is parallel to the argument. Here, 'parallel to'
850 | // means that the argument's direction is either parallel or antiparallel to
851 | // the line's own direction. A line is parallel to a plane if the two do not
852 | // have a unique intersection.
853 | isParallelTo: function(obj) {
854 | if (obj.normal) { return obj.isParallelTo(this); }
855 | var theta = this.direction.angleFrom(obj.direction);
856 | return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision);
857 | },
858 |
859 | // Returns the line's perpendicular distance from the argument,
860 | // which can be a point, a line or a plane
861 | distanceFrom: function(obj) {
862 | if (obj.normal) { return obj.distanceFrom(this); }
863 | if (obj.direction) {
864 | // obj is a line
865 | if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); }
866 | var N = this.direction.cross(obj.direction).toUnitVector().elements;
867 | var A = this.anchor.elements, B = obj.anchor.elements;
868 | return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
869 | } else {
870 | // obj is a point
871 | var P = obj.elements || obj;
872 | var A = this.anchor.elements, D = this.direction.elements;
873 | var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2];
874 | var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3);
875 | if (modPA === 0) return 0;
876 | // Assumes direction vector is normalized
877 | var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA;
878 | var sin2 = 1 - cosTheta*cosTheta;
879 | return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2));
880 | }
881 | },
882 |
883 | // Returns true iff the argument is a point on the line
884 | contains: function(point) {
885 | var dist = this.distanceFrom(point);
886 | return (dist !== null && dist <= Sylvester.precision);
887 | },
888 |
889 | // Returns true iff the line lies in the given plane
890 | liesIn: function(plane) {
891 | return plane.contains(this);
892 | },
893 |
894 | // Returns true iff the line has a unique point of intersection with the argument
895 | intersects: function(obj) {
896 | if (obj.normal) { return obj.intersects(this); }
897 | return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision);
898 | },
899 |
900 | // Returns the unique intersection point with the argument, if one exists
901 | intersectionWith: function(obj) {
902 | if (obj.normal) { return obj.intersectionWith(this); }
903 | if (!this.intersects(obj)) { return null; }
904 | var P = this.anchor.elements, X = this.direction.elements,
905 | Q = obj.anchor.elements, Y = obj.direction.elements;
906 | var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2];
907 | var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2];
908 | var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3;
909 | var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3;
910 | var XdotX = X1*X1 + X2*X2 + X3*X3;
911 | var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3;
912 | var XdotY = X1*Y1 + X2*Y2 + X3*Y3;
913 | var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY);
914 | return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]);
915 | },
916 |
917 | // Returns the point on the line that is closest to the given point or line
918 | pointClosestTo: function(obj) {
919 | if (obj.direction) {
920 | // obj is a line
921 | if (this.intersects(obj)) { return this.intersectionWith(obj); }
922 | if (this.isParallelTo(obj)) { return null; }
923 | var D = this.direction.elements, E = obj.direction.elements;
924 | var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2];
925 | // Create plane containing obj and the shared normal and intersect this with it
926 | // Thank you: http://www.cgafaq.info/wiki/Line-line_distance
927 | var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2);
928 | var N = Vector.create([x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1]);
929 | var P = Plane.create(obj.anchor, N);
930 | return P.intersectionWith(this);
931 | } else {
932 | // obj is a point
933 | var P = obj.elements || obj;
934 | if (this.contains(P)) { return Vector.create(P); }
935 | var A = this.anchor.elements, D = this.direction.elements;
936 | var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2];
937 | var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2),
938 | z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3);
939 | var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]);
940 | var k = this.distanceFrom(P) / V.modulus();
941 | return Vector.create([
942 | P[0] + V.elements[0] * k,
943 | P[1] + V.elements[1] * k,
944 | (P[2] || 0) + V.elements[2] * k
945 | ]);
946 | }
947 | },
948 |
949 | // Returns a copy of the line rotated by t radians about the given line. Works by
950 | // finding the argument's closest point to this line's anchor point (call this C) and
951 | // rotating the anchor about C. Also rotates the line's direction about the argument's.
952 | // Be careful with this - the rotation axis' direction affects the outcome!
953 | rotate: function(t, line) {
954 | // If we're working in 2D
955 | if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); }
956 | var R = Matrix.Rotation(t, line.direction).elements;
957 | var C = line.pointClosestTo(this.anchor).elements;
958 | var A = this.anchor.elements, D = this.direction.elements;
959 | var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
960 | var x = A1 - C1, y = A2 - C2, z = A3 - C3;
961 | return Line.create([
962 | C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
963 | C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
964 | C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
965 | ], [
966 | R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2],
967 | R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2],
968 | R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2]
969 | ]);
970 | },
971 |
972 | // Returns the line's reflection in the given point or line
973 | reflectionIn: function(obj) {
974 | if (obj.normal) {
975 | // obj is a plane
976 | var A = this.anchor.elements, D = this.direction.elements;
977 | var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2];
978 | var newA = this.anchor.reflectionIn(obj).elements;
979 | // Add the line's direction vector to its anchor, then mirror that in the plane
980 | var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3;
981 | var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements;
982 | var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]];
983 | return Line.create(newA, newD);
984 | } else if (obj.direction) {
985 | // obj is a line - reflection obtained by rotating PI radians about obj
986 | return this.rotate(Math.PI, obj);
987 | } else {
988 | // obj is a point - just reflect the line's anchor in it
989 | var P = obj.elements || obj;
990 | return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction);
991 | }
992 | },
993 |
994 | // Set the line's anchor point and direction.
995 | setVectors: function(anchor, direction) {
996 | // Need to do this so that line's properties are not
997 | // references to the arguments passed in
998 | anchor = Vector.create(anchor);
999 | direction = Vector.create(direction);
1000 | if (anchor.elements.length == 2) {anchor.elements.push(0); }
1001 | if (direction.elements.length == 2) { direction.elements.push(0); }
1002 | if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; }
1003 | var mod = direction.modulus();
1004 | if (mod === 0) { return null; }
1005 | this.anchor = anchor;
1006 | this.direction = Vector.create([
1007 | direction.elements[0] / mod,
1008 | direction.elements[1] / mod,
1009 | direction.elements[2] / mod
1010 | ]);
1011 | return this;
1012 | }
1013 | };
1014 |
1015 |
1016 | // Constructor function
1017 | Line.create = function(anchor, direction) {
1018 | var L = new Line();
1019 | return L.setVectors(anchor, direction);
1020 | };
1021 |
1022 | // Axes
1023 | Line.X = Line.create(Vector.Zero(3), Vector.i);
1024 | Line.Y = Line.create(Vector.Zero(3), Vector.j);
1025 | Line.Z = Line.create(Vector.Zero(3), Vector.k);
1026 |
1027 |
1028 |
1029 | function Plane() {}
1030 | Plane.prototype = {
1031 |
1032 | // Returns true iff the plane occupies the same space as the argument
1033 | eql: function(plane) {
1034 | return (this.contains(plane.anchor) && this.isParallelTo(plane));
1035 | },
1036 |
1037 | // Returns a copy of the plane
1038 | dup: function() {
1039 | return Plane.create(this.anchor, this.normal);
1040 | },
1041 |
1042 | // Returns the result of translating the plane by the given vector
1043 | translate: function(vector) {
1044 | var V = vector.elements || vector;
1045 | return Plane.create([
1046 | this.anchor.elements[0] + V[0],
1047 | this.anchor.elements[1] + V[1],
1048 | this.anchor.elements[2] + (V[2] || 0)
1049 | ], this.normal);
1050 | },
1051 |
1052 | // Returns true iff the plane is parallel to the argument. Will return true
1053 | // if the planes are equal, or if you give a line and it lies in the plane.
1054 | isParallelTo: function(obj) {
1055 | var theta;
1056 | if (obj.normal) {
1057 | // obj is a plane
1058 | theta = this.normal.angleFrom(obj.normal);
1059 | return (Math.abs(theta) <= Sylvester.precision || Math.abs(Math.PI - theta) <= Sylvester.precision);
1060 | } else if (obj.direction) {
1061 | // obj is a line
1062 | return this.normal.isPerpendicularTo(obj.direction);
1063 | }
1064 | return null;
1065 | },
1066 |
1067 | // Returns true iff the receiver is perpendicular to the argument
1068 | isPerpendicularTo: function(plane) {
1069 | var theta = this.normal.angleFrom(plane.normal);
1070 | return (Math.abs(Math.PI/2 - theta) <= Sylvester.precision);
1071 | },
1072 |
1073 | // Returns the plane's distance from the given object (point, line or plane)
1074 | distanceFrom: function(obj) {
1075 | if (this.intersects(obj) || this.contains(obj)) { return 0; }
1076 | if (obj.anchor) {
1077 | // obj is a plane or line
1078 | var A = this.anchor.elements, B = obj.anchor.elements, N = this.normal.elements;
1079 | return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
1080 | } else {
1081 | // obj is a point
1082 | var P = obj.elements || obj;
1083 | var A = this.anchor.elements, N = this.normal.elements;
1084 | return Math.abs((A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]);
1085 | }
1086 | },
1087 |
1088 | // Returns true iff the plane contains the given point or line
1089 | contains: function(obj) {
1090 | if (obj.normal) { return null; }
1091 | if (obj.direction) {
1092 | return (this.contains(obj.anchor) && this.contains(obj.anchor.add(obj.direction)));
1093 | } else {
1094 | var P = obj.elements || obj;
1095 | var A = this.anchor.elements, N = this.normal.elements;
1096 | var diff = Math.abs(N[0]*(A[0] - P[0]) + N[1]*(A[1] - P[1]) + N[2]*(A[2] - (P[2] || 0)));
1097 | return (diff <= Sylvester.precision);
1098 | }
1099 | },
1100 |
1101 | // Returns true iff the plane has a unique point/line of intersection with the argument
1102 | intersects: function(obj) {
1103 | if (typeof(obj.direction) == 'undefined' && typeof(obj.normal) == 'undefined') { return null; }
1104 | return !this.isParallelTo(obj);
1105 | },
1106 |
1107 | // Returns the unique intersection with the argument, if one exists. The result
1108 | // will be a vector if a line is supplied, and a line if a plane is supplied.
1109 | intersectionWith: function(obj) {
1110 | if (!this.intersects(obj)) { return null; }
1111 | if (obj.direction) {
1112 | // obj is a line
1113 | var A = obj.anchor.elements, D = obj.direction.elements,
1114 | P = this.anchor.elements, N = this.normal.elements;
1115 | var multiplier = (N[0]*(P[0]-A[0]) + N[1]*(P[1]-A[1]) + N[2]*(P[2]-A[2])) / (N[0]*D[0] + N[1]*D[1] + N[2]*D[2]);
1116 | return Vector.create([A[0] + D[0]*multiplier, A[1] + D[1]*multiplier, A[2] + D[2]*multiplier]);
1117 | } else if (obj.normal) {
1118 | // obj is a plane
1119 | var direction = this.normal.cross(obj.normal).toUnitVector();
1120 | // To find an anchor point, we find one co-ordinate that has a value
1121 | // of zero somewhere on the intersection, and remember which one we picked
1122 | var N = this.normal.elements, A = this.anchor.elements,
1123 | O = obj.normal.elements, B = obj.anchor.elements;
1124 | var solver = Matrix.Zero(2,2), i = 0;
1125 | while (solver.isSingular()) {
1126 | i++;
1127 | solver = Matrix.create([
1128 | [ N[i%3], N[(i+1)%3] ],
1129 | [ O[i%3], O[(i+1)%3] ]
1130 | ]);
1131 | }
1132 | // Then we solve the simultaneous equations in the remaining dimensions
1133 | var inverse = solver.inverse().elements;
1134 | var x = N[0]*A[0] + N[1]*A[1] + N[2]*A[2];
1135 | var y = O[0]*B[0] + O[1]*B[1] + O[2]*B[2];
1136 | var intersection = [
1137 | inverse[0][0] * x + inverse[0][1] * y,
1138 | inverse[1][0] * x + inverse[1][1] * y
1139 | ];
1140 | var anchor = [];
1141 | for (var j = 1; j <= 3; j++) {
1142 | // This formula picks the right element from intersection by
1143 | // cycling depending on which element we set to zero above
1144 | anchor.push((i == j) ? 0 : intersection[(j + (5 - i)%3)%3]);
1145 | }
1146 | return Line.create(anchor, direction);
1147 | }
1148 | },
1149 |
1150 | // Returns the point in the plane closest to the given point
1151 | pointClosestTo: function(point) {
1152 | var P = point.elements || point;
1153 | var A = this.anchor.elements, N = this.normal.elements;
1154 | var dot = (A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2];
1155 | return Vector.create([P[0] + N[0] * dot, P[1] + N[1] * dot, (P[2] || 0) + N[2] * dot]);
1156 | },
1157 |
1158 | // Returns a copy of the plane, rotated by t radians about the given line
1159 | // See notes on Line#rotate.
1160 | rotate: function(t, line) {
1161 | var R = Matrix.Rotation(t, line.direction).elements;
1162 | var C = line.pointClosestTo(this.anchor).elements;
1163 | var A = this.anchor.elements, N = this.normal.elements;
1164 | var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
1165 | var x = A1 - C1, y = A2 - C2, z = A3 - C3;
1166 | return Plane.create([
1167 | C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
1168 | C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
1169 | C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
1170 | ], [
1171 | R[0][0] * N[0] + R[0][1] * N[1] + R[0][2] * N[2],
1172 | R[1][0] * N[0] + R[1][1] * N[1] + R[1][2] * N[2],
1173 | R[2][0] * N[0] + R[2][1] * N[1] + R[2][2] * N[2]
1174 | ]);
1175 | },
1176 |
1177 | // Returns the reflection of the plane in the given point, line or plane.
1178 | reflectionIn: function(obj) {
1179 | if (obj.normal) {
1180 | // obj is a plane
1181 | var A = this.anchor.elements, N = this.normal.elements;
1182 | var A1 = A[0], A2 = A[1], A3 = A[2], N1 = N[0], N2 = N[1], N3 = N[2];
1183 | var newA = this.anchor.reflectionIn(obj).elements;
1184 | // Add the plane's normal to its anchor, then mirror that in the other plane
1185 | var AN1 = A1 + N1, AN2 = A2 + N2, AN3 = A3 + N3;
1186 | var Q = obj.pointClosestTo([AN1, AN2, AN3]).elements;
1187 | var newN = [Q[0] + (Q[0] - AN1) - newA[0], Q[1] + (Q[1] - AN2) - newA[1], Q[2] + (Q[2] - AN3) - newA[2]];
1188 | return Plane.create(newA, newN);
1189 | } else if (obj.direction) {
1190 | // obj is a line
1191 | return this.rotate(Math.PI, obj);
1192 | } else {
1193 | // obj is a point
1194 | var P = obj.elements || obj;
1195 | return Plane.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.normal);
1196 | }
1197 | },
1198 |
1199 | // Sets the anchor point and normal to the plane. If three arguments are specified,
1200 | // the normal is calculated by assuming the three points should lie in the same plane.
1201 | // If only two are sepcified, the second is taken to be the normal. Normal vector is
1202 | // normalised before storage.
1203 | setVectors: function(anchor, v1, v2) {
1204 | anchor = Vector.create(anchor);
1205 | anchor = anchor.to3D(); if (anchor === null) { return null; }
1206 | v1 = Vector.create(v1);
1207 | v1 = v1.to3D(); if (v1 === null) { return null; }
1208 | if (typeof(v2) == 'undefined') {
1209 | v2 = null;
1210 | } else {
1211 | v2 = Vector.create(v2);
1212 | v2 = v2.to3D(); if (v2 === null) { return null; }
1213 | }
1214 | var A1 = anchor.elements[0], A2 = anchor.elements[1], A3 = anchor.elements[2];
1215 | var v11 = v1.elements[0], v12 = v1.elements[1], v13 = v1.elements[2];
1216 | var normal, mod;
1217 | if (v2 !== null) {
1218 | var v21 = v2.elements[0], v22 = v2.elements[1], v23 = v2.elements[2];
1219 | normal = Vector.create([
1220 | (v12 - A2) * (v23 - A3) - (v13 - A3) * (v22 - A2),
1221 | (v13 - A3) * (v21 - A1) - (v11 - A1) * (v23 - A3),
1222 | (v11 - A1) * (v22 - A2) - (v12 - A2) * (v21 - A1)
1223 | ]);
1224 | mod = normal.modulus();
1225 | if (mod === 0) { return null; }
1226 | normal = Vector.create([normal.elements[0] / mod, normal.elements[1] / mod, normal.elements[2] / mod]);
1227 | } else {
1228 | mod = Math.sqrt(v11*v11 + v12*v12 + v13*v13);
1229 | if (mod === 0) { return null; }
1230 | normal = Vector.create([v1.elements[0] / mod, v1.elements[1] / mod, v1.elements[2] / mod]);
1231 | }
1232 | this.anchor = anchor;
1233 | this.normal = normal;
1234 | return this;
1235 | }
1236 | };
1237 |
1238 | // Constructor function
1239 | Plane.create = function(anchor, v1, v2) {
1240 | var P = new Plane();
1241 | return P.setVectors(anchor, v1, v2);
1242 | };
1243 |
1244 | // X-Y-Z planes
1245 | Plane.XY = Plane.create(Vector.Zero(3), Vector.k);
1246 | Plane.YZ = Plane.create(Vector.Zero(3), Vector.i);
1247 | Plane.ZX = Plane.create(Vector.Zero(3), Vector.j);
1248 | Plane.YX = Plane.XY; Plane.ZY = Plane.YZ; Plane.XZ = Plane.ZX;
1249 |
1250 | // Utility functions
1251 | var $V = Vector.create;
1252 | var $M = Matrix.create;
1253 | var $L = Line.create;
1254 | var $P = Plane.create;
1255 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/jquery.transform.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery 2d Transform v0.9.3
3 | * http://wiki.github.com/heygrady/transform/
4 | *
5 | * Copyright 2010, Grady Kuhnline
6 | * Dual licensed under the MIT or GPL Version 2 licenses.
7 | * http://jquery.org/license
8 | *
9 | * Date: Sat Dec 4 15:46:09 2010 -0800
10 | */
11 | ///////////////////////////////////////////////////////
12 | // Transform
13 | ///////////////////////////////////////////////////////
14 | (function($, window, document, undefined) {
15 | /**
16 | * @var Regex identify the matrix filter in IE
17 | */
18 | var rmatrix = /progid:DXImageTransform\.Microsoft\.Matrix\(.*?\)/,
19 | rfxnum = /^([\+\-]=)?([\d+.\-]+)(.*)$/,
20 | rperc = /%/;
21 |
22 | // Steal some code from Modernizr
23 | var m = document.createElement( 'modernizr' ),
24 | m_style = m.style;
25 |
26 | function stripUnits(arg) {
27 | return parseFloat(arg);
28 | }
29 |
30 | /**
31 | * Find the prefix that this browser uses
32 | */
33 | function getVendorPrefix() {
34 | var property = {
35 | transformProperty : '',
36 | MozTransform : '-moz-',
37 | WebkitTransform : '-webkit-',
38 | OTransform : '-o-',
39 | msTransform : '-ms-'
40 | };
41 | for (var p in property) {
42 | if (typeof m_style[p] != 'undefined') {
43 | return property[p];
44 | }
45 | }
46 | return null;
47 | }
48 |
49 | function supportCssTransforms() {
50 | if (typeof(window.Modernizr) !== 'undefined') {
51 | return Modernizr.csstransforms;
52 | }
53 |
54 | var props = [ 'transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ];
55 | for ( var i in props ) {
56 | if ( m_style[ props[i] ] !== undefined ) {
57 | return true;
58 | }
59 | }
60 | }
61 |
62 | // Capture some basic properties
63 | var vendorPrefix = getVendorPrefix(),
64 | transformProperty = vendorPrefix !== null ? vendorPrefix + 'transform' : false,
65 | transformOriginProperty = vendorPrefix !== null ? vendorPrefix + 'transform-origin' : false;
66 |
67 | // store support in the jQuery Support object
68 | $.support.csstransforms = supportCssTransforms();
69 |
70 | // IE9 public preview 6 requires the DOM names
71 | if (vendorPrefix == '-ms-') {
72 | transformProperty = 'msTransform';
73 | transformOriginProperty = 'msTransformOrigin';
74 | }
75 |
76 | /**
77 | * Class for creating cross-browser transformations
78 | * @constructor
79 | */
80 | $.extend({
81 | transform: function(elem) {
82 | // Cache the transform object on the element itself
83 | elem.transform = this;
84 |
85 | /**
86 | * The element we're working with
87 | * @var jQueryCollection
88 | */
89 | this.$elem = $(elem);
90 |
91 | /**
92 | * Remember the matrix we're applying to help the safeOuterLength func
93 | */
94 | this.applyingMatrix = false;
95 | this.matrix = null;
96 |
97 | /**
98 | * Remember the css height and width to save time
99 | * This is only really used in IE
100 | * @var Number
101 | */
102 | this.height = null;
103 | this.width = null;
104 | this.outerHeight = null;
105 | this.outerWidth = null;
106 |
107 | /**
108 | * We need to know the box-sizing in IE for building the outerHeight and outerWidth
109 | * @var string
110 | */
111 | this.boxSizingValue = null;
112 | this.boxSizingProperty = null;
113 |
114 | this.attr = null;
115 | this.transformProperty = transformProperty;
116 | this.transformOriginProperty = transformOriginProperty;
117 | }
118 | });
119 |
120 | $.extend($.transform, {
121 | /**
122 | * @var Array list of all valid transform functions
123 | */
124 | funcs: ['matrix', 'origin', 'reflect', 'reflectX', 'reflectXY', 'reflectY', 'rotate', 'scale', 'scaleX', 'scaleY', 'skew', 'skewX', 'skewY', 'translate', 'translateX', 'translateY']
125 | });
126 |
127 | /**
128 | * Create Transform as a jQuery plugin
129 | * @param Object funcs
130 | * @param Object options
131 | */
132 | $.fn.transform = function(funcs, options) {
133 | return this.each(function() {
134 | var t = this.transform || new $.transform(this);
135 | if (funcs) {
136 | t.exec(funcs, options);
137 | }
138 | });
139 | };
140 |
141 | $.transform.prototype = {
142 | /**
143 | * Applies all of the transformations
144 | * @param Object funcs
145 | * @param Object options
146 | * forceMatrix - uses the matrix in all browsers
147 | * preserve - tries to preserve the values from previous runs
148 | */
149 | exec: function(funcs, options) {
150 | // extend options
151 | options = $.extend(true, {
152 | forceMatrix: false,
153 | preserve: false
154 | }, options);
155 |
156 | // preserve the funcs from the previous run
157 | this.attr = null;
158 | if (options.preserve) {
159 | funcs = $.extend(true, this.getAttrs(true, true), funcs);
160 | } else {
161 | funcs = $.extend(true, {}, funcs); // copy the object to prevent weirdness
162 | }
163 |
164 | // Record the custom attributes on the element itself
165 | this.setAttrs(funcs);
166 |
167 | // apply the funcs
168 | if ($.support.csstransforms && !options.forceMatrix) {
169 | // CSS3 is supported
170 | return this.execFuncs(funcs);
171 | } else if ($.browser.msie || ($.support.csstransforms && options.forceMatrix)) {
172 | // Internet Explorer or Forced matrix
173 | return this.execMatrix(funcs);
174 | }
175 | return false;
176 | },
177 |
178 | /**
179 | * Applies all of the transformations as functions
180 | * @param Object funcs
181 | */
182 | execFuncs: function(funcs) {
183 | var values = [];
184 |
185 | // construct a CSS string
186 | for (var func in funcs) {
187 | // handle origin separately
188 | if (func == 'origin') {
189 | this[func].apply(this, $.isArray(funcs[func]) ? funcs[func] : [funcs[func]]);
190 | } else if ($.inArray(func, $.transform.funcs) !== -1) {
191 | values.push(this.createTransformFunc(func, funcs[func]));
192 | }
193 | }
194 | this.$elem.css(transformProperty, values.join(' '));
195 | return true;
196 | },
197 |
198 | /**
199 | * Applies all of the transformations as a matrix
200 | * @param Object funcs
201 | */
202 | execMatrix: function(funcs) {
203 | var matrix,
204 | tempMatrix,
205 | args;
206 |
207 | var elem = this.$elem[0],
208 | _this = this;
209 | function normalPixels(val, i) {
210 | if (rperc.test(val)) {
211 | // this really only applies to translation
212 | return parseFloat(val) / 100 * _this['safeOuter' + (i ? 'Height' : 'Width')]();
213 | }
214 | return toPx(elem, val);
215 | }
216 |
217 | var rtranslate = /translate[X|Y]?/,
218 | trans = [];
219 |
220 | for (var func in funcs) {
221 | switch ($.type(funcs[func])) {
222 | case 'array': args = funcs[func]; break;
223 | case 'string': args = $.map(funcs[func].split(','), $.trim); break;
224 | default: args = [funcs[func]];
225 | }
226 |
227 | if ($.matrix[func]) {
228 |
229 | if ($.cssAngle[func]) {
230 | // normalize on degrees
231 | args = $.map(args, $.angle.toDegree);
232 | } else if (!$.cssNumber[func]) {
233 | // normalize to pixels
234 | args = $.map(args, normalPixels);
235 | } else {
236 | // strip units
237 | args = $.map(args, stripUnits);
238 | }
239 |
240 | tempMatrix = $.matrix[func].apply(this, args);
241 | if (rtranslate.test(func)) {
242 | //defer translation
243 | trans.push(tempMatrix);
244 | } else {
245 | matrix = matrix ? matrix.x(tempMatrix) : tempMatrix;
246 | }
247 | } else if (func == 'origin') {
248 | this[func].apply(this, args);
249 | }
250 | }
251 |
252 | // check that we have a matrix
253 | matrix = matrix || $.matrix.identity();
254 |
255 | // Apply translation
256 | $.each(trans, function(i, val) { matrix = matrix.x(val); });
257 |
258 | // pull out the relevant values
259 | var a = parseFloat(matrix.e(1,1).toFixed(6)),
260 | b = parseFloat(matrix.e(2,1).toFixed(6)),
261 | c = parseFloat(matrix.e(1,2).toFixed(6)),
262 | d = parseFloat(matrix.e(2,2).toFixed(6)),
263 | tx = matrix.rows === 3 ? parseFloat(matrix.e(1,3).toFixed(6)) : 0,
264 | ty = matrix.rows === 3 ? parseFloat(matrix.e(2,3).toFixed(6)) : 0;
265 |
266 | //apply the transform to the element
267 | if ($.support.csstransforms && vendorPrefix === '-moz-') {
268 | // -moz-
269 | this.$elem.css(transformProperty, 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx + 'px, ' + ty + 'px)');
270 | } else if ($.support.csstransforms) {
271 | // -webkit, -o-, w3c
272 | // NOTE: WebKit and Opera don't allow units on the translate variables
273 | this.$elem.css(transformProperty, 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx + ', ' + ty + ')');
274 | } else if ($.browser.msie) {
275 | // IE requires the special transform Filter
276 |
277 | //TODO: Use Nearest Neighbor during animation FilterType=\'nearest neighbor\'
278 | var filterType = ', FilterType=\'nearest neighbor\''; //bilinear
279 | var style = this.$elem[0].style;
280 | var matrixFilter = 'progid:DXImageTransform.Microsoft.Matrix(' +
281 | 'M11=' + a + ', M12=' + c + ', M21=' + b + ', M22=' + d +
282 | ', sizingMethod=\'auto expand\'' + filterType + ')';
283 | var filter = style.filter || $.curCSS( this.$elem[0], "filter" ) || "";
284 | style.filter = rmatrix.test(filter) ? filter.replace(rmatrix, matrixFilter) : filter ? filter + ' ' + matrixFilter : matrixFilter;
285 |
286 | // Let's know that we're applying post matrix fixes and the height/width will be static for a bit
287 | this.applyingMatrix = true;
288 | this.matrix = matrix;
289 |
290 | // IE can't set the origin or translate directly
291 | this.fixPosition(matrix, tx, ty);
292 |
293 | this.applyingMatrix = false;
294 | this.matrix = null;
295 | }
296 | return true;
297 | },
298 |
299 | /**
300 | * Sets the transform-origin
301 | * This really needs to be percentages
302 | * @param Number x length
303 | * @param Number y length
304 | */
305 | origin: function(x, y) {
306 | // use CSS in supported browsers
307 | if ($.support.csstransforms) {
308 | if (typeof y === 'undefined') {
309 | this.$elem.css(transformOriginProperty, x);
310 | } else {
311 | this.$elem.css(transformOriginProperty, x + ' ' + y);
312 | }
313 | return true;
314 | }
315 |
316 | // correct for keyword lengths
317 | switch (x) {
318 | case 'left': x = '0'; break;
319 | case 'right': x = '100%'; break;
320 | case 'center': // no break
321 | case undefined: x = '50%';
322 | }
323 | switch (y) {
324 | case 'top': y = '0'; break;
325 | case 'bottom': y = '100%'; break;
326 | case 'center': // no break
327 | case undefined: y = '50%'; //TODO: does this work?
328 | }
329 |
330 | // store mixed values with units, assumed pixels
331 | this.setAttr('origin', [
332 | rperc.test(x) ? x : toPx(this.$elem[0], x) + 'px',
333 | rperc.test(y) ? y : toPx(this.$elem[0], y) + 'px'
334 | ]);
335 | //console.log(this.getAttr('origin'));
336 | return true;
337 | },
338 |
339 | /**
340 | * Create a function suitable for a CSS value
341 | * @param string func
342 | * @param Mixed value
343 | */
344 | createTransformFunc: function(func, value) {
345 | if (func.substr(0, 7) === 'reflect') {
346 | // let's fake reflection, false value
347 | // falsey sets an identity matrix
348 | var m = value ? $.matrix[func]() : $.matrix.identity();
349 | return 'matrix(' + m.e(1,1) + ', ' + m.e(2,1) + ', ' + m.e(1,2) + ', ' + m.e(2,2) + ', 0, 0)';
350 | }
351 |
352 | //value = _correctUnits(func, value);
353 |
354 | if (func == 'matrix') {
355 | if (vendorPrefix === '-moz-') {
356 | value[4] = value[4] ? value[4] + 'px' : 0;
357 | value[5] = value[5] ? value[5] + 'px' : 0;
358 | }
359 | }
360 | return func + '(' + ($.isArray(value) ? value.join(', ') : value) + ')';
361 | },
362 |
363 | /**
364 | * @param Matrix matrix
365 | * @param Number tx
366 | * @param Number ty
367 | * @param Number height
368 | * @param Number width
369 | */
370 | fixPosition: function(matrix, tx, ty, height, width) {
371 | // now we need to fix it!
372 | var calc = new $.matrix.calc(matrix, this.safeOuterHeight(), this.safeOuterWidth()),
373 | origin = this.getAttr('origin'); // mixed percentages and px
374 |
375 | // translate a 0, 0 origin to the current origin
376 | var offset = calc.originOffset(new $.matrix.V2(
377 | rperc.test(origin[0]) ? parseFloat(origin[0])/100*calc.outerWidth : parseFloat(origin[0]),
378 | rperc.test(origin[1]) ? parseFloat(origin[1])/100*calc.outerHeight : parseFloat(origin[1])
379 | ));
380 |
381 | // IE glues the top-most and left-most pixels of the transformed object to top/left of the original object
382 | //TODO: This seems wrong in the calculations
383 | var sides = calc.sides();
384 |
385 | // Protect against an item that is already positioned
386 | var cssPosition = this.$elem.css('position');
387 | if (cssPosition == 'static') {
388 | cssPosition = 'relative';
389 | }
390 |
391 | //TODO: if the element is already positioned, we should attempt to respect it (somehow)
392 | //NOTE: we could preserve our offset top and left in an attr on the elem
393 | var pos = {top: 0, left: 0};
394 |
395 | // Approximates transform-origin, tx, and ty
396 | var css = {
397 | 'position': cssPosition,
398 | 'top': (offset.top + ty + sides.top + pos.top) + 'px',
399 | 'left': (offset.left + tx + sides.left + pos.left) + 'px',
400 | 'zoom': 1
401 | };
402 |
403 | this.$elem.css(css);
404 | }
405 | };
406 |
407 | /**
408 | * Ensure that values have the appropriate units on them
409 | * @param string func
410 | * @param Mixed value
411 | */
412 | function toPx(elem, val) {
413 | var parts = rfxnum.exec($.trim(val));
414 |
415 | if (parts[3] && parts[3] !== 'px') {
416 | var prop = 'paddingBottom',
417 | orig = $.style( elem, prop );
418 |
419 | $.style( elem, prop, val );
420 | val = cur( elem, prop );
421 | $.style( elem, prop, orig );
422 | return val;
423 | }
424 | return parseFloat( val );
425 | }
426 |
427 | function cur(elem, prop) {
428 | if ( elem[prop] != null && (!elem.style || elem.style[prop] == null) ) {
429 | return elem[ prop ];
430 | }
431 |
432 | var r = parseFloat( $.css( elem, prop ) );
433 | return r && r > -10000 ? r : 0;
434 | }
435 | })(jQuery, this, this.document);
436 |
437 |
438 | ///////////////////////////////////////////////////////
439 | // Safe Outer Length
440 | ///////////////////////////////////////////////////////
441 | (function($, window, document, undefined) {
442 | $.extend($.transform.prototype, {
443 | /**
444 | * @param void
445 | * @return Number
446 | */
447 | safeOuterHeight: function() {
448 | return this.safeOuterLength('height');
449 | },
450 |
451 | /**
452 | * @param void
453 | * @return Number
454 | */
455 | safeOuterWidth: function() {
456 | return this.safeOuterLength('width');
457 | },
458 |
459 | /**
460 | * Returns reliable outer dimensions for an object that may have been transformed.
461 | * Only use this if the matrix isn't handy
462 | * @param String dim height or width
463 | * @return Number
464 | */
465 | safeOuterLength: function(dim) {
466 | var funcName = 'outer' + (dim == 'width' ? 'Width' : 'Height');
467 |
468 | if (!$.support.csstransforms && $.browser.msie) {
469 | // make the variables more generic
470 | dim = dim == 'width' ? 'width' : 'height';
471 |
472 | // if we're transforming and have a matrix; we can shortcut.
473 | // the true outerHeight is the transformed outerHeight divided by the ratio.
474 | // the ratio is equal to the height of a 1px by 1px box that has been transformed by the same matrix.
475 | if (this.applyingMatrix && !this[funcName] && this.matrix) {
476 | // calculate and return the correct size
477 | var calc = new $.matrix.calc(this.matrix, 1, 1),
478 | ratio = calc.offset(),
479 | length = this.$elem[funcName]() / ratio[dim];
480 | this[funcName] = length;
481 |
482 | return length;
483 | } else if (this.applyingMatrix && this[funcName]) {
484 | // return the cached calculation
485 | return this[funcName];
486 | }
487 |
488 | // map dimensions to box sides
489 | var side = {
490 | height: ['top', 'bottom'],
491 | width: ['left', 'right']
492 | };
493 |
494 | // setup some variables
495 | var elem = this.$elem[0],
496 | outerLen = parseFloat($.curCSS(elem, dim, true)), //TODO: this can be cached on animations that do not animate height/width
497 | boxSizingProp = this.boxSizingProperty,
498 | boxSizingValue = this.boxSizingValue;
499 |
500 | // IE6 && IE7 will never have a box-sizing property, so fake it
501 | if (!this.boxSizingProperty) {
502 | boxSizingProp = this.boxSizingProperty = _findBoxSizingProperty() || 'box-sizing';
503 | boxSizingValue = this.boxSizingValue = this.$elem.css(boxSizingProp) || 'content-box';
504 | }
505 |
506 | // return it immediately if we already know it
507 | if (this[funcName] && this[dim] == outerLen) {
508 | return this[funcName];
509 | } else {
510 | this[dim] = outerLen;
511 | }
512 |
513 | // add in the padding and border
514 | if (boxSizingProp && (boxSizingValue == 'padding-box' || boxSizingValue == 'content-box')) {
515 | outerLen += parseFloat($.curCSS(elem, 'padding-' + side[dim][0], true)) || 0 +
516 | parseFloat($.curCSS(elem, 'padding-' + side[dim][1], true)) || 0;
517 | }
518 | if (boxSizingProp && boxSizingValue == 'content-box') {
519 | outerLen += parseFloat($.curCSS(elem, 'border-' + side[dim][0] + '-width', true)) || 0 +
520 | parseFloat($.curCSS(elem, 'border-' + side[dim][1] + '-width', true)) || 0;
521 | }
522 |
523 | // remember and return the outerHeight
524 | this[funcName] = outerLen;
525 | return outerLen;
526 | }
527 | return this.$elem[funcName]();
528 | }
529 | });
530 |
531 | /**
532 | * Determine the correct property for checking the box-sizing property
533 | * @param void
534 | * @return string
535 | */
536 | var _boxSizingProperty = null;
537 | function _findBoxSizingProperty () {
538 | if (_boxSizingProperty) {
539 | return _boxSizingProperty;
540 | }
541 |
542 | var property = {
543 | boxSizing : 'box-sizing',
544 | MozBoxSizing : '-moz-box-sizing',
545 | WebkitBoxSizing : '-webkit-box-sizing',
546 | OBoxSizing : '-o-box-sizing'
547 | },
548 | elem = document.body;
549 |
550 | for (var p in property) {
551 | if (typeof elem.style[p] != 'undefined') {
552 | _boxSizingProperty = property[p];
553 | return _boxSizingProperty;
554 | }
555 | }
556 | return null;
557 | }
558 | })(jQuery, this, this.document);
559 |
560 |
561 | ///////////////////////////////////////////////////////
562 | // Attr
563 | ///////////////////////////////////////////////////////
564 | (function($, window, document, undefined) {
565 | var rfuncvalue = /([\w\-]*?)\((.*?)\)/g, // with values
566 | attr = 'data-transform',
567 | rspace = /\s/,
568 | rcspace = /,\s?/;
569 |
570 | $.extend($.transform.prototype, {
571 | /**
572 | * This overrides all of the attributes
573 | * @param Object funcs a list of transform functions to store on this element
574 | * @return void
575 | */
576 | setAttrs: function(funcs) {
577 | var string = '',
578 | value;
579 | for (var func in funcs) {
580 | value = funcs[func];
581 | if ($.isArray(value)) {
582 | value = value.join(', ');
583 | }
584 | string += ' ' + func + '(' + value + ')';
585 | }
586 | this.attr = $.trim(string);
587 | this.$elem.attr(attr, this.attr);
588 | },
589 |
590 | /**
591 | * This sets only a specific atribute
592 | * @param string func name of a transform function
593 | * @param mixed value with proper units
594 | * @return void
595 | */
596 | setAttr: function(func, value) {
597 | // stringify the value
598 | if ($.isArray(value)) {
599 | value = value.join(', ');
600 | }
601 |
602 | // pull from a local variable to look it up
603 | var transform = this.attr || this.$elem.attr(attr);
604 | if (!transform || transform.indexOf(func) == -1) {
605 | // we don't have any existing values, save it
606 | // we don't have this function yet, save it
607 | this.attr = $.trim(transform + ' ' + func + '(' + value + ')');
608 | this.$elem.attr(attr, this.attr);
609 | } else {
610 | // replace the existing value
611 | var funcs = [], parts;
612 |
613 | // regex split
614 | rfuncvalue.lastIndex = 0; // reset the regex pointer
615 | while (parts = rfuncvalue.exec(transform)) {
616 | if (func == parts[1]) {
617 | funcs.push(func + '(' + value + ')');
618 | } else {
619 | funcs.push(parts[0]);
620 | }
621 | }
622 | this.attr = funcs.join(' ');
623 | this.$elem.attr(attr, this.attr);
624 | }
625 | },
626 |
627 | /**
628 | * @return Object
629 | */
630 | getAttrs: function() {
631 | var transform = this.attr || this.$elem.attr(attr);
632 | if (!transform) {
633 | // We don't have any existing values, return empty object
634 | return {};
635 | }
636 |
637 | // replace the existing value
638 | var attrs = {}, parts, value;
639 |
640 | rfuncvalue.lastIndex = 0; // reset the regex pointer
641 | while ((parts = rfuncvalue.exec(transform)) !== null) {
642 | if (parts) {
643 | value = parts[2].split(rcspace);
644 | attrs[parts[1]] = value.length == 1 ? value[0] : value;
645 | }
646 | }
647 | return attrs;
648 | },
649 |
650 | /**
651 | * @param String func
652 | * @return mixed
653 | */
654 | getAttr: function(func) {
655 | var attrs = this.getAttrs();
656 | if (typeof attrs[func] !== 'undefined') {
657 | return attrs[func];
658 | }
659 |
660 | //TODO: move the origin to a function
661 | if (func === 'origin' && $.support.csstransforms) {
662 | // supported browsers return percentages always
663 | return this.$elem.css(this.transformOriginProperty).split(rspace);
664 | } else if (func === 'origin') {
665 | // just force IE to also return a percentage
666 | return ['50%', '50%'];
667 | }
668 |
669 | return $.cssDefault[func] || 0;
670 | }
671 | });
672 |
673 | // Define
674 | if (typeof($.cssAngle) == 'undefined') {
675 | $.cssAngle = {};
676 | }
677 | $.extend($.cssAngle, {
678 | rotate: true,
679 | skew: true,
680 | skewX: true,
681 | skewY: true
682 | });
683 |
684 | // Define default values
685 | if (typeof($.cssDefault) == 'undefined') {
686 | $.cssDefault = {};
687 | }
688 |
689 | $.extend($.cssDefault, {
690 | scale: [1, 1],
691 | scaleX: 1,
692 | scaleY: 1,
693 | matrix: [1, 0, 0, 1, 0, 0],
694 | origin: ['50%', '50%'], // TODO: allow this to be a function, like get
695 | reflect: [1, 0, 0, 1, 0, 0],
696 | reflectX: [1, 0, 0, 1, 0, 0],
697 | reflectXY: [1, 0, 0, 1, 0, 0],
698 | reflectY: [1, 0, 0, 1, 0, 0]
699 | });
700 |
701 | // Define functons with multiple values
702 | if (typeof($.cssMultipleValues) == 'undefined') {
703 | $.cssMultipleValues = {};
704 | }
705 | $.extend($.cssMultipleValues, {
706 | matrix: 6,
707 | origin: {
708 | length: 2,
709 | duplicate: true
710 | },
711 | reflect: 6,
712 | reflectX: 6,
713 | reflectXY: 6,
714 | reflectY: 6,
715 | scale: {
716 | length: 2,
717 | duplicate: true
718 | },
719 | skew: 2,
720 | translate: 2
721 | });
722 |
723 | // specify unitless funcs
724 | $.extend($.cssNumber, {
725 | matrix: true,
726 | reflect: true,
727 | reflectX: true,
728 | reflectXY: true,
729 | reflectY: true,
730 | scale: true,
731 | scaleX: true,
732 | scaleY: true
733 | });
734 |
735 | // override all of the css functions
736 | $.each($.transform.funcs, function(i, func) {
737 | $.cssHooks[func] = {
738 | set: function(elem, value) {
739 | var transform = elem.transform || new $.transform(elem),
740 | funcs = {};
741 | funcs[func] = value;
742 | transform.exec(funcs, {preserve: true});
743 | },
744 | get: function(elem, computed) {
745 | var transform = elem.transform || new $.transform(elem);
746 | return transform.getAttr(func);
747 | }
748 | };
749 | });
750 |
751 | // Support Reflection animation better by returning a matrix
752 | $.each(['reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) {
753 | $.cssHooks[func].get = function(elem, computed) {
754 | var transform = elem.transform || new $.transform(elem);
755 | return transform.getAttr('matrix') || $.cssDefault[func];
756 | };
757 | });
758 | })(jQuery, this, this.document);
759 | ///////////////////////////////////////////////////////
760 | // Animation
761 | ///////////////////////////////////////////////////////
762 | (function($, window, document, undefined) {
763 | /**
764 | * @var Regex looks for units on a string
765 | */
766 | var rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/;
767 |
768 | /**
769 | * Doctors prop values in the event that they contain spaces
770 | * @param Object prop
771 | * @param String speed
772 | * @param String easing
773 | * @param Function callback
774 | * @return bool
775 | */
776 | var _animate = $.fn.animate;
777 | $.fn.animate = function( prop, speed, easing, callback ) {
778 | var optall = $.speed(speed, easing, callback),
779 | mv = $.cssMultipleValues;
780 |
781 | // Speed always creates a complete function that must be reset
782 | optall.complete = optall.old;
783 |
784 | // Capture multiple values
785 | if (!$.isEmptyObject(prop)) {
786 | if (typeof optall.original === 'undefined') {
787 | optall.original = {};
788 | }
789 | $.each( prop, function( name, val ) {
790 | if (mv[name]
791 | || $.cssAngle[name]
792 | || (!$.cssNumber[name] && $.inArray(name, $.transform.funcs) !== -1)) {
793 |
794 | // Handle special easing
795 | var specialEasing = null;
796 | if (jQuery.isArray(prop[name])) {
797 | var mvlen = 1, len = val.length;
798 | if (mv[name]) {
799 | mvlen = (typeof mv[name].length === 'undefined' ? mv[name] : mv[name].length);
800 | }
801 | if ( len > mvlen
802 | || (len < mvlen && len == 2)
803 | || (len == 2 && mvlen == 2 && isNaN(parseFloat(val[len - 1])))) {
804 |
805 | specialEasing = val[len - 1];
806 | val.splice(len - 1, 1);
807 | }
808 | }
809 |
810 | // Store the original values onto the optall
811 | optall.original[name] = val.toString();
812 |
813 | // reduce to a unitless number (to trick animate)
814 | prop[name] = parseFloat(val);
815 | }
816 | } );
817 | }
818 |
819 | //NOTE: we edited prop above to trick animate
820 | //NOTE: we pre-convert to an optall so we can doctor it
821 | return _animate.apply(this, [arguments[0], optall]);
822 | };
823 |
824 | var prop = 'paddingBottom';
825 | function cur(elem, prop) {
826 | if ( elem[prop] != null && (!elem.style || elem.style[prop] == null) ) {
827 | //return elem[ prop ];
828 | }
829 |
830 | var r = parseFloat( $.css( elem, prop ) );
831 | return r && r > -10000 ? r : 0;
832 | }
833 |
834 | var _custom = $.fx.prototype.custom;
835 | $.fx.prototype.custom = function(from, to, unit) {
836 | var multiple = $.cssMultipleValues[this.prop],
837 | angle = $.cssAngle[this.prop];
838 |
839 | //TODO: simply check for the existence of CSS Hooks?
840 | if (multiple || (!$.cssNumber[this.prop] && $.inArray(this.prop, $.transform.funcs) !== -1)) {
841 | this.values = [];
842 |
843 | if (!multiple) {
844 | multiple = 1;
845 | }
846 |
847 | // Pull out the known values
848 | var values = this.options.original[this.prop],
849 | currentValues = $(this.elem).css(this.prop),
850 | defaultValues = $.cssDefault[this.prop] || 0;
851 |
852 | // make sure the current css value is an array
853 | if (!$.isArray(currentValues)) {
854 | currentValues = [currentValues];
855 | }
856 |
857 | // make sure the new values are an array
858 | if (!$.isArray(values)) {
859 | if ($.type(values) === 'string') {
860 | values = values.split(',');
861 | } else {
862 | values = [values];
863 | }
864 | }
865 |
866 | // make sure we have enough new values
867 | var length = multiple.length || multiple, i = 0;
868 | while (values.length < length) {
869 | values.push(multiple.duplicate ? values[0] : defaultValues[i] || 0);
870 | i++;
871 | }
872 |
873 | // calculate a start, end and unit for each new value
874 | var start, parts, end, //unit,
875 | fx = this,
876 | transform = fx.elem.transform;
877 | orig = $.style(fx.elem, prop);
878 |
879 | $.each(values, function(i, val) {
880 | // find a sensible start value
881 | if (currentValues[i]) {
882 | start = currentValues[i];
883 | } else if (defaultValues[i] && !multiple.duplicate) {
884 | start = defaultValues[i];
885 | } else if (multiple.duplicate) {
886 | start = currentValues[0];
887 | } else {
888 | start = 0;
889 | }
890 |
891 | // Force the correct unit on the start
892 | if (angle) {
893 | start = $.angle.toDegree(start);
894 | } else if (!$.cssNumber[fx.prop]) {
895 | parts = rfxnum.exec($.trim(start));
896 | if (parts[3] && parts[3] !== 'px') {
897 | if (parts[3] === '%') {
898 | start = parseFloat( parts[2] ) / 100 * transform['safeOuter' + (i ? 'Height' : 'Width')]();
899 | } else {
900 | $.style( fx.elem, prop, start);
901 | start = cur(fx.elem, prop);
902 | $.style( fx.elem, prop, orig);
903 | }
904 | }
905 | }
906 | start = parseFloat(start);
907 |
908 | // parse the value with a regex
909 | parts = rfxnum.exec($.trim(val));
910 |
911 | if (parts) {
912 | // we found a sensible value and unit
913 | end = parseFloat( parts[2] );
914 | unit = parts[3] || "px"; //TODO: change to an appropriate default unit
915 |
916 | if (angle) {
917 | end = $.angle.toDegree(end + unit);
918 | unit = 'deg';
919 | } else if (!$.cssNumber[fx.prop] && unit === '%') {
920 | start = (start / transform['safeOuter' + (i ? 'Height' : 'Width')]()) * 100;
921 | } else if (!$.cssNumber[fx.prop] && unit !== 'px') {
922 | $.style( fx.elem, prop, (end || 1) + unit);
923 | start = ((end || 1) / cur(fx.elem, prop)) * start;
924 | $.style( fx.elem, prop, orig);
925 | }
926 |
927 | // If a +=/-= token was provided, we're doing a relative animation
928 | if (parts[1]) {
929 | end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
930 | }
931 | } else {
932 | // I don't know when this would happen
933 | end = val;
934 | unit = '';
935 | }
936 |
937 | // Save the values
938 | fx.values.push({
939 | start: start,
940 | end: end,
941 | unit: unit
942 | });
943 | });
944 | }
945 | return _custom.apply(this, arguments);
946 | };
947 |
948 | /**
949 | * Animates a multi value attribute
950 | * @param Object fx
951 | * @return null
952 | */
953 | $.fx.multipleValueStep = {
954 | _default: function(fx) {
955 | $.each(fx.values, function(i, val) {
956 | fx.values[i].now = val.start + ((val.end - val.start) * fx.pos);
957 | });
958 | }
959 | };
960 | $.each(['matrix', 'reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) {
961 | $.fx.multipleValueStep[func] = function(fx) {
962 | var d = fx.decomposed,
963 | $m = $.matrix;
964 | m = $m.identity();
965 |
966 | d.now = {};
967 |
968 | // increment each part of the decomposition and recompose it
969 | $.each(d.start, function(k) {
970 | // calculate the current value
971 | d.now[k] = parseFloat(d.start[k]) + ((parseFloat(d.end[k]) - parseFloat(d.start[k])) * fx.pos);
972 |
973 | // skip functions that won't affect the transform
974 | if (((k === 'scaleX' || k === 'scaleY') && d.now[k] === 1) ||
975 | (k !== 'scaleX' && k !== 'scaleY' && d.now[k] === 0)) {
976 | return true;
977 | }
978 |
979 | // calculating
980 | m = m.x($m[k](d.now[k]));
981 | });
982 |
983 | // save the correct matrix values for the value of now
984 | var val;
985 | $.each(fx.values, function(i) {
986 | switch (i) {
987 | case 0: val = parseFloat(m.e(1, 1).toFixed(6)); break;
988 | case 1: val = parseFloat(m.e(2, 1).toFixed(6)); break;
989 | case 2: val = parseFloat(m.e(1, 2).toFixed(6)); break;
990 | case 3: val = parseFloat(m.e(2, 2).toFixed(6)); break;
991 | case 4: val = parseFloat(m.e(1, 3).toFixed(6)); break;
992 | case 5: val = parseFloat(m.e(2, 3).toFixed(6)); break;
993 | }
994 | fx.values[i].now = val;
995 | });
996 | };
997 | });
998 | /**
999 | * Step for animating tranformations
1000 | */
1001 | $.each($.transform.funcs, function(i, func) {
1002 | $.fx.step[func] = function(fx) {
1003 | var transform = fx.elem.transform || new $.transform(fx.elem),
1004 | funcs = {};
1005 |
1006 | if ($.cssMultipleValues[func] || (!$.cssNumber[func] && $.inArray(func, $.transform.funcs) !== -1)) {
1007 | ($.fx.multipleValueStep[fx.prop] || $.fx.multipleValueStep._default)(fx);
1008 | funcs[fx.prop] = [];
1009 | $.each(fx.values, function(i, val) {
1010 | funcs[fx.prop].push(val.now + ($.cssNumber[fx.prop] ? '' : val.unit));
1011 | });
1012 | } else {
1013 | funcs[fx.prop] = fx.now + ($.cssNumber[fx.prop] ? '' : fx.unit);
1014 | }
1015 |
1016 | transform.exec(funcs, {preserve: true});
1017 | };
1018 | });
1019 |
1020 | // Support matrix animation
1021 | $.each(['matrix', 'reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) {
1022 | $.fx.step[func] = function(fx) {
1023 | var transform = fx.elem.transform || new $.transform(fx.elem),
1024 | funcs = {};
1025 |
1026 | if (!fx.initialized) {
1027 | fx.initialized = true;
1028 |
1029 | // Reflections need a sensible end value set
1030 | if (func !== 'matrix') {
1031 | var values = $.matrix[func]().elements;
1032 | var val;
1033 | $.each(fx.values, function(i) {
1034 | switch (i) {
1035 | case 0: val = values[0]; break;
1036 | case 1: val = values[2]; break;
1037 | case 2: val = values[1]; break;
1038 | case 3: val = values[3]; break;
1039 | default: val = 0;
1040 | }
1041 | fx.values[i].end = val;
1042 | });
1043 | }
1044 |
1045 | // Decompose the start and end
1046 | fx.decomposed = {};
1047 | var v = fx.values;
1048 |
1049 | fx.decomposed.start = $.matrix.matrix(v[0].start, v[1].start, v[2].start, v[3].start, v[4].start, v[5].start).decompose();
1050 | fx.decomposed.end = $.matrix.matrix(v[0].end, v[1].end, v[2].end, v[3].end, v[4].end, v[5].end).decompose();
1051 | }
1052 |
1053 | ($.fx.multipleValueStep[fx.prop] || $.fx.multipleValueStep._default)(fx);
1054 | funcs.matrix = [];
1055 | $.each(fx.values, function(i, val) {
1056 | funcs.matrix.push(val.now);
1057 | });
1058 |
1059 | transform.exec(funcs, {preserve: true});
1060 | };
1061 | });
1062 | })(jQuery, this, this.document);
1063 | ///////////////////////////////////////////////////////
1064 | // Angle
1065 | ///////////////////////////////////////////////////////
1066 | (function($, window, document, undefined) {
1067 | /**
1068 | * Converting a radian to a degree
1069 | * @const
1070 | */
1071 | var RAD_DEG = 180/Math.PI;
1072 |
1073 | /**
1074 | * Converting a radian to a grad
1075 | * @const
1076 | */
1077 | var RAD_GRAD = 200/Math.PI;
1078 |
1079 | /**
1080 | * Converting a degree to a radian
1081 | * @const
1082 | */
1083 | var DEG_RAD = Math.PI/180;
1084 |
1085 | /**
1086 | * Converting a degree to a grad
1087 | * @const
1088 | */
1089 | var DEG_GRAD = 2/1.8;
1090 |
1091 | /**
1092 | * Converting a grad to a degree
1093 | * @const
1094 | */
1095 | var GRAD_DEG = 0.9;
1096 |
1097 | /**
1098 | * Converting a grad to a radian
1099 | * @const
1100 | */
1101 | var GRAD_RAD = Math.PI/200;
1102 |
1103 |
1104 | var rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/;
1105 |
1106 | /**
1107 | * Functions for converting angles
1108 | * @var Object
1109 | */
1110 | $.extend({
1111 | angle: {
1112 | /**
1113 | * available units for an angle
1114 | * @var Regex
1115 | */
1116 | runit: /(deg|g?rad)/,
1117 |
1118 | /**
1119 | * Convert a radian into a degree
1120 | * @param Number rad
1121 | * @return Number
1122 | */
1123 | radianToDegree: function(rad) {
1124 | return rad * RAD_DEG;
1125 | },
1126 |
1127 | /**
1128 | * Convert a radian into a degree
1129 | * @param Number rad
1130 | * @return Number
1131 | */
1132 | radianToGrad: function(rad) {
1133 | return rad * RAD_GRAD;
1134 | },
1135 |
1136 | /**
1137 | * Convert a degree into a radian
1138 | * @param Number deg
1139 | * @return Number
1140 | */
1141 | degreeToRadian: function(deg) {
1142 | return deg * DEG_RAD;
1143 | },
1144 |
1145 | /**
1146 | * Convert a degree into a radian
1147 | * @param Number deg
1148 | * @return Number
1149 | */
1150 | degreeToGrad: function(deg) {
1151 | return deg * DEG_GRAD;
1152 | },
1153 |
1154 | /**
1155 | * Convert a grad into a degree
1156 | * @param Number grad
1157 | * @return Number
1158 | */
1159 | gradToDegree: function(grad) {
1160 | return grad * GRAD_DEG;
1161 | },
1162 |
1163 | /**
1164 | * Convert a grad into a radian
1165 | * @param Number grad
1166 | * @return Number
1167 | */
1168 | gradToRadian: function(grad) {
1169 | return grad * GRAD_RAD;
1170 | },
1171 |
1172 | /**
1173 | * Convert an angle with a unit to a degree
1174 | * @param String val angle with a unit
1175 | * @return Number
1176 | */
1177 | toDegree: function (val) {
1178 | var parts = rfxnum.exec(val);
1179 | if (parts) {
1180 | val = parseFloat( parts[2] );
1181 | switch (parts[3] || 'deg') {
1182 | case 'grad':
1183 | val = $.angle.gradToDegree(val);
1184 | break;
1185 | case 'rad':
1186 | val = $.angle.radianToDegree(val);
1187 | break;
1188 | }
1189 | return val;
1190 | }
1191 | return 0;
1192 | }
1193 | }
1194 | });
1195 | })(jQuery, this, this.document);
1196 | ///////////////////////////////////////////////////////
1197 | // Matrix
1198 | ///////////////////////////////////////////////////////
1199 | (function($, window, document, undefined) {
1200 | /**
1201 | * Matrix object for creating matrices relevant for 2d Transformations
1202 | * @var Object
1203 | */
1204 | if (typeof($.matrix) == 'undefined') {
1205 | $.extend({
1206 | matrix: {}
1207 | });
1208 | }
1209 | var $m = $.matrix;
1210 |
1211 | $.extend( $m, {
1212 | /**
1213 | * A 2-value vector
1214 | * @param Number x
1215 | * @param Number y
1216 | * @constructor
1217 | */
1218 | V2: function(x, y){
1219 | if ($.isArray(arguments[0])) {
1220 | this.elements = arguments[0].slice(0, 2);
1221 | } else {
1222 | this.elements = [x, y];
1223 | }
1224 | this.length = 2;
1225 | },
1226 |
1227 | /**
1228 | * A 2-value vector
1229 | * @param Number x
1230 | * @param Number y
1231 | * @param Number z
1232 | * @constructor
1233 | */
1234 | V3: function(x, y, z){
1235 | if ($.isArray(arguments[0])) {
1236 | this.elements = arguments[0].slice(0, 3);
1237 | } else {
1238 | this.elements = [x, y, z];
1239 | }
1240 | this.length = 3;
1241 | },
1242 |
1243 | /**
1244 | * A 2x2 Matrix, useful for 2D-transformations without translations
1245 | * @param Number mn
1246 | * @constructor
1247 | */
1248 | M2x2: function(m11, m12, m21, m22) {
1249 | if ($.isArray(arguments[0])) {
1250 | this.elements = arguments[0].slice(0, 4);
1251 | } else {
1252 | this.elements = Array.prototype.slice.call(arguments).slice(0, 4);
1253 | }
1254 | this.rows = 2;
1255 | this.cols = 2;
1256 | },
1257 |
1258 | /**
1259 | * A 3x3 Matrix, useful for 3D-transformations without translations
1260 | * @param Number mn
1261 | * @constructor
1262 | */
1263 | M3x3: function(m11, m12, m13, m21, m22, m23, m31, m32, m33) {
1264 | if ($.isArray(arguments[0])) {
1265 | this.elements = arguments[0].slice(0, 9);
1266 | } else {
1267 | this.elements = Array.prototype.slice.call(arguments).slice(0, 9);
1268 | }
1269 | this.rows = 3;
1270 | this.cols = 3;
1271 | }
1272 | });
1273 |
1274 | /** generic matrix prototype */
1275 | var Matrix = {
1276 | /**
1277 | * Return a specific element from the matrix
1278 | * @param Number row where 1 is the 0th row
1279 | * @param Number col where 1 is the 0th column
1280 | * @return Number
1281 | */
1282 | e: function(row, col) {
1283 | var rows = this.rows,
1284 | cols = this.cols;
1285 |
1286 | // return 0 on nonsense rows and columns
1287 | if (row > rows || col > rows || row < 1 || col < 1) {
1288 | return 0;
1289 | }
1290 |
1291 | return this.elements[(row - 1) * cols + col - 1];
1292 | },
1293 |
1294 | /**
1295 | * Taken from Zoomooz
1296 | * https://github.com/jaukia/zoomooz/blob/c7a37b9a65a06ba730bd66391bbd6fe8e55d3a49/js/jquery.zoomooz.js
1297 | */
1298 | decompose: function() {
1299 | var a = this.e(1, 1),
1300 | b = this.e(2, 1),
1301 | c = this.e(1, 2),
1302 | d = this.e(2, 2),
1303 | e = this.e(1, 3),
1304 | f = this.e(2, 3);
1305 |
1306 | // In case the matrix can't be decomposed
1307 | if (Math.abs(a * d - b * c) < 0.01) {
1308 | return {
1309 | rotate: 0 + 'deg',
1310 | skewX: 0 + 'deg',
1311 | scaleX: 1,
1312 | scaleY: 1,
1313 | translateX: 0 + 'px',
1314 | translateY: 0 + 'px'
1315 | };
1316 | }
1317 |
1318 | // Translate is easy
1319 | var tx = e, ty = f;
1320 |
1321 | // factor out the X scale
1322 | var sx = Math.sqrt(a * a + b * b);
1323 | a = a/sx;
1324 | b = b/sx;
1325 |
1326 | // factor out the skew
1327 | var k = a * c + b * d;
1328 | c -= a * k;
1329 | d -= b * k;
1330 |
1331 | // factor out the Y scale
1332 | var sy = Math.sqrt(c * c + d * d);
1333 | c = c / sy;
1334 | d = d / sy;
1335 | k = k / sy;
1336 |
1337 | // account for negative scale
1338 | if ((a * d - b * c) < 0.0) {
1339 | a = -a;
1340 | b = -b;
1341 | //c = -c; // accomplishes nothing to negate it
1342 | //d = -d; // accomplishes nothing to negate it
1343 | sx = -sx;
1344 | //sy = -sy //Scale Y shouldn't ever be negated
1345 | }
1346 |
1347 | // calculate the rotation angle and skew angle
1348 | var rad2deg = $.angle.radianToDegree;
1349 | var r = rad2deg(Math.atan2(b, a));
1350 | k = rad2deg(Math.atan(k));
1351 |
1352 | return {
1353 | rotate: r + 'deg',
1354 | skewX: k + 'deg',
1355 | scaleX: sx,
1356 | scaleY: sy,
1357 | translateX: tx + 'px',
1358 | translateY: ty + 'px'
1359 | };
1360 | }
1361 | };
1362 |
1363 | /** Extend all of the matrix types with the same prototype */
1364 | $.extend($m.M2x2.prototype, Matrix, {
1365 | toM3x3: function() {
1366 | var a = this.elements;
1367 | return new $m.M3x3(
1368 | a[0], a[1], 0,
1369 | a[2], a[3], 0,
1370 | 0, 0, 1
1371 | );
1372 | },
1373 |
1374 | /**
1375 | * Multiply a 2x2 matrix by a similar matrix or a vector
1376 | * @param M2x2 | V2 matrix
1377 | * @return M2x2 | V2
1378 | */
1379 | x: function(matrix) {
1380 | var isVector = typeof(matrix.rows) === 'undefined';
1381 |
1382 | // Ensure the right-sized matrix
1383 | if (!isVector && matrix.rows == 3) {
1384 | return this.toM3x3().x(matrix);
1385 | }
1386 |
1387 | var a = this.elements,
1388 | b = matrix.elements;
1389 |
1390 | if (isVector && b.length == 2) {
1391 | // b is actually a vector
1392 | return new $m.V2(
1393 | a[0] * b[0] + a[1] * b[1],
1394 | a[2] * b[0] + a[3] * b[1]
1395 | );
1396 | } else if (b.length == a.length) {
1397 | // b is a 2x2 matrix
1398 | return new $m.M2x2(
1399 | a[0] * b[0] + a[1] * b[2],
1400 | a[0] * b[1] + a[1] * b[3],
1401 |
1402 | a[2] * b[0] + a[3] * b[2],
1403 | a[2] * b[1] + a[3] * b[3]
1404 | );
1405 | }
1406 | return false; // fail
1407 | },
1408 |
1409 | /**
1410 | * Generates an inverse of the current matrix
1411 | * @param void
1412 | * @return M2x2
1413 | * @link http://www.dr-lex.be/random/matrix_inv.html
1414 | */
1415 | inverse: function() {
1416 | var d = 1/this.determinant(),
1417 | a = this.elements;
1418 | return new $m.M2x2(
1419 | d * a[3], d * -a[1],
1420 | d * -a[2], d * a[0]
1421 | );
1422 | },
1423 |
1424 | /**
1425 | * Calculates the determinant of the current matrix
1426 | * @param void
1427 | * @return Number
1428 | * @link http://www.dr-lex.be/random/matrix_inv.html
1429 | */
1430 | determinant: function() {
1431 | var a = this.elements;
1432 | return a[0] * a[3] - a[1] * a[2];
1433 | }
1434 | });
1435 |
1436 | $.extend($m.M3x3.prototype, Matrix, {
1437 | /**
1438 | * Multiply a 3x3 matrix by a similar matrix or a vector
1439 | * @param M3x3 | V3 matrix
1440 | * @return M3x3 | V3
1441 | */
1442 | x: function(matrix) {
1443 | var isVector = typeof(matrix.rows) === 'undefined';
1444 |
1445 | // Ensure the right-sized matrix
1446 | if (!isVector && matrix.rows < 3) {
1447 | matrix = matrix.toM3x3();
1448 | }
1449 |
1450 | var a = this.elements,
1451 | b = matrix.elements;
1452 |
1453 | if (isVector && b.length == 3) {
1454 | // b is actually a vector
1455 | return new $m.V3(
1456 | a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
1457 | a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
1458 | a[6] * b[0] + a[7] * b[1] + a[8] * b[2]
1459 | );
1460 | } else if (b.length == a.length) {
1461 | // b is a 3x3 matrix
1462 | return new $m.M3x3(
1463 | a[0] * b[0] + a[1] * b[3] + a[2] * b[6],
1464 | a[0] * b[1] + a[1] * b[4] + a[2] * b[7],
1465 | a[0] * b[2] + a[1] * b[5] + a[2] * b[8],
1466 |
1467 | a[3] * b[0] + a[4] * b[3] + a[5] * b[6],
1468 | a[3] * b[1] + a[4] * b[4] + a[5] * b[7],
1469 | a[3] * b[2] + a[4] * b[5] + a[5] * b[8],
1470 |
1471 | a[6] * b[0] + a[7] * b[3] + a[8] * b[6],
1472 | a[6] * b[1] + a[7] * b[4] + a[8] * b[7],
1473 | a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
1474 | );
1475 | }
1476 | return false; // fail
1477 | },
1478 |
1479 | /**
1480 | * Generates an inverse of the current matrix
1481 | * @param void
1482 | * @return M3x3
1483 | * @link http://www.dr-lex.be/random/matrix_inv.html
1484 | */
1485 | inverse: function() {
1486 | var d = 1/this.determinant(),
1487 | a = this.elements;
1488 | return new $m.M3x3(
1489 | d * ( a[8] * a[4] - a[7] * a[5]),
1490 | d * (-(a[8] * a[1] - a[7] * a[2])),
1491 | d * ( a[5] * a[1] - a[4] * a[2]),
1492 |
1493 | d * (-(a[8] * a[3] - a[6] * a[5])),
1494 | d * ( a[8] * a[0] - a[6] * a[2]),
1495 | d * (-(a[5] * a[0] - a[3] * a[2])),
1496 |
1497 | d * ( a[7] * a[3] - a[6] * a[4]),
1498 | d * (-(a[7] * a[0] - a[6] * a[1])),
1499 | d * ( a[4] * a[0] - a[3] * a[1])
1500 | );
1501 | },
1502 |
1503 | /**
1504 | * Calculates the determinant of the current matrix
1505 | * @param void
1506 | * @return Number
1507 | * @link http://www.dr-lex.be/random/matrix_inv.html
1508 | */
1509 | determinant: function() {
1510 | var a = this.elements;
1511 | return a[0] * (a[8] * a[4] - a[7] * a[5]) - a[3] * (a[8] * a[1] - a[7] * a[2]) + a[6] * (a[5] * a[1] - a[4] * a[2]);
1512 | }
1513 | });
1514 |
1515 | /** generic vector prototype */
1516 | var Vector = {
1517 | /**
1518 | * Return a specific element from the vector
1519 | * @param Number i where 1 is the 0th value
1520 | * @return Number
1521 | */
1522 | e: function(i) {
1523 | return this.elements[i - 1];
1524 | }
1525 | };
1526 |
1527 | /** Extend all of the vector types with the same prototype */
1528 | $.extend($m.V2.prototype, Vector);
1529 | $.extend($m.V3.prototype, Vector);
1530 | })(jQuery, this, this.document);
1531 | ///////////////////////////////////////////////////////
1532 | // Matrix Calculations
1533 | ///////////////////////////////////////////////////////
1534 | (function($, window, document, undefined) {
1535 | /**
1536 | * Matrix object for creating matrices relevant for 2d Transformations
1537 | * @var Object
1538 | */
1539 | if (typeof($.matrix) == 'undefined') {
1540 | $.extend({
1541 | matrix: {}
1542 | });
1543 | }
1544 |
1545 | $.extend( $.matrix, {
1546 | /**
1547 | * Class for calculating coordinates on a matrix
1548 | * @param Matrix matrix
1549 | * @param Number outerHeight
1550 | * @param Number outerWidth
1551 | * @constructor
1552 | */
1553 | calc: function(matrix, outerHeight, outerWidth) {
1554 | /**
1555 | * @var Matrix
1556 | */
1557 | this.matrix = matrix;
1558 |
1559 | /**
1560 | * @var Number
1561 | */
1562 | this.outerHeight = outerHeight;
1563 |
1564 | /**
1565 | * @var Number
1566 | */
1567 | this.outerWidth = outerWidth;
1568 | }
1569 | });
1570 |
1571 | $.matrix.calc.prototype = {
1572 | /**
1573 | * Calculate a coord on the new object
1574 | * @return Object
1575 | */
1576 | coord: function(x, y, z) {
1577 | //default z and w
1578 | z = typeof(z) !== 'undefined' ? z : 0;
1579 |
1580 | var matrix = this.matrix,
1581 | vector;
1582 |
1583 | switch (matrix.rows) {
1584 | case 2:
1585 | vector = matrix.x(new $.matrix.V2(x, y));
1586 | break;
1587 | case 3:
1588 | vector = matrix.x(new $.matrix.V3(x, y, z));
1589 | break;
1590 | }
1591 |
1592 | return vector;
1593 | },
1594 |
1595 | /**
1596 | * Calculate the corners of the new object
1597 | * @return Object
1598 | */
1599 | corners: function(x, y) {
1600 | // Try to save the corners if this is called a lot
1601 | var save = !(typeof(x) !=='undefined' || typeof(y) !=='undefined'),
1602 | c;
1603 | if (!this.c || !save) {
1604 | y = y || this.outerHeight;
1605 | x = x || this.outerWidth;
1606 |
1607 | c = {
1608 | tl: this.coord(0, 0),
1609 | bl: this.coord(0, y),
1610 | tr: this.coord(x, 0),
1611 | br: this.coord(x, y)
1612 | };
1613 | } else {
1614 | c = this.c;
1615 | }
1616 |
1617 | if (save) {
1618 | this.c = c;
1619 | }
1620 | return c;
1621 | },
1622 |
1623 | /**
1624 | * Calculate the sides of the new object
1625 | * @return Object
1626 | */
1627 | sides: function(corners) {
1628 | // The corners of the box
1629 | var c = corners || this.corners();
1630 |
1631 | return {
1632 | top: Math.min(c.tl.e(2), c.tr.e(2), c.br.e(2), c.bl.e(2)),
1633 | bottom: Math.max(c.tl.e(2), c.tr.e(2), c.br.e(2), c.bl.e(2)),
1634 | left: Math.min(c.tl.e(1), c.tr.e(1), c.br.e(1), c.bl.e(1)),
1635 | right: Math.max(c.tl.e(1), c.tr.e(1), c.br.e(1), c.bl.e(1))
1636 | };
1637 | },
1638 |
1639 | /**
1640 | * Calculate the offset of the new object
1641 | * @return Object
1642 | */
1643 | offset: function(corners) {
1644 | // The corners of the box
1645 | var s = this.sides(corners);
1646 |
1647 | // return size
1648 | return {
1649 | height: Math.abs(s.bottom - s.top),
1650 | width: Math.abs(s.right - s.left)
1651 | };
1652 | },
1653 |
1654 | /**
1655 | * Calculate the area of the new object
1656 | * @return Number
1657 | * @link http://en.wikipedia.org/wiki/Quadrilateral#Area_of_a_convex_quadrilateral
1658 | */
1659 | area: function(corners) {
1660 | // The corners of the box
1661 | var c = corners || this.corners();
1662 |
1663 | // calculate the two diagonal vectors
1664 | var v1 = {
1665 | x: c.tr.e(1) - c.tl.e(1) + c.br.e(1) - c.bl.e(1),
1666 | y: c.tr.e(2) - c.tl.e(2) + c.br.e(2) - c.bl.e(2)
1667 | },
1668 | v2 = {
1669 | x: c.bl.e(1) - c.tl.e(1) + c.br.e(1) - c.tr.e(1),
1670 | y: c.bl.e(2) - c.tl.e(2) + c.br.e(2) - c.tr.e(2)
1671 | };
1672 |
1673 | return 0.25 * Math.abs(v1.e(1) * v2.e(2) - v1.e(2) * v2.e(1));
1674 | },
1675 |
1676 | /**
1677 | * Calculate the non-affinity of the new object
1678 | * @return Number
1679 | */
1680 | nonAffinity: function() {
1681 | // The corners of the box
1682 | var sides = this.sides(),
1683 | xDiff = sides.top - sides.bottom,
1684 | yDiff = sides.left - sides.right;
1685 |
1686 | return parseFloat(parseFloat(Math.abs(
1687 | (Math.pow(xDiff, 2) + Math.pow(yDiff, 2)) /
1688 | (sides.top * sides.bottom + sides.left * sides.right)
1689 | )).toFixed(8));
1690 | },
1691 |
1692 | /**
1693 | * Calculate a proper top and left for IE
1694 | * @param Object toOrigin
1695 | * @param Object fromOrigin
1696 | * @return Object
1697 | */
1698 | originOffset: function(toOrigin, fromOrigin) {
1699 | // the origin to translate to
1700 | toOrigin = toOrigin ? toOrigin : new $.matrix.V2(
1701 | this.outerWidth * 0.5,
1702 | this.outerHeight * 0.5
1703 | );
1704 |
1705 | // the origin to translate from (IE has a fixed origin of 0, 0)
1706 | fromOrigin = fromOrigin ? fromOrigin : new $.matrix.V2(
1707 | 0,
1708 | 0
1709 | );
1710 |
1711 | // transform the origins
1712 | var toCenter = this.coord(toOrigin.e(1), toOrigin.e(2));
1713 | var fromCenter = this.coord(fromOrigin.e(1), fromOrigin.e(2));
1714 |
1715 | // return the offset
1716 | return {
1717 | top: (fromCenter.e(2) - fromOrigin.e(2)) - (toCenter.e(2) - toOrigin.e(2)),
1718 | left: (fromCenter.e(1) - fromOrigin.e(1)) - (toCenter.e(1) - toOrigin.e(1))
1719 | };
1720 | }
1721 | };
1722 | })(jQuery, this, this.document);
1723 | ///////////////////////////////////////////////////////
1724 | // 2d Matrix Functions
1725 | ///////////////////////////////////////////////////////
1726 | (function($, window, document, undefined) {
1727 | /**
1728 | * Matrix object for creating matrices relevant for 2d Transformations
1729 | * @var Object
1730 | */
1731 | if (typeof($.matrix) == 'undefined') {
1732 | $.extend({
1733 | matrix: {}
1734 | });
1735 | }
1736 | var $m = $.matrix,
1737 | $m2x2 = $m.M2x2,
1738 | $m3x3 = $m.M3x3;
1739 |
1740 | $.extend( $m, {
1741 | /**
1742 | * Identity matrix
1743 | * @param Number size
1744 | * @return Matrix
1745 | */
1746 | identity: function(size) {
1747 | size = size || 2;
1748 | var length = size * size,
1749 | elements = new Array(length),
1750 | mod = size + 1;
1751 | for (var i = 0; i < length; i++) {
1752 | elements[i] = (i % mod) === 0 ? 1 : 0;
1753 | }
1754 | return new $m['M'+size+'x'+size](elements);
1755 | },
1756 |
1757 | /**
1758 | * Matrix
1759 | * @return Matrix
1760 | */
1761 | matrix: function() {
1762 | var args = Array.prototype.slice.call(arguments);
1763 | // arguments are in column-major order
1764 | switch (arguments.length) {
1765 | case 4:
1766 | return new $m2x2(
1767 | args[0], args[2],
1768 | args[1], args[3]
1769 | );
1770 | case 6:
1771 | return new $m3x3(
1772 | args[0], args[2], args[4],
1773 | args[1], args[3], args[5],
1774 | 0, 0, 1
1775 | );
1776 | }
1777 | },
1778 |
1779 | /**
1780 | * Reflect (same as rotate(180))
1781 | * @return Matrix
1782 | */
1783 | reflect: function() {
1784 | return new $m2x2(
1785 | -1, 0,
1786 | 0, -1
1787 | );
1788 | },
1789 |
1790 | /**
1791 | * Reflect across the x-axis (mirrored upside down)
1792 | * @return Matrix
1793 | */
1794 | reflectX: function() {
1795 | return new $m2x2(
1796 | 1, 0,
1797 | 0, -1
1798 | );
1799 | },
1800 |
1801 | /**
1802 | * Reflect by swapping x an y (same as reflectX + rotate(-90))
1803 | * @return Matrix
1804 | */
1805 | reflectXY: function() {
1806 | return new $m2x2(
1807 | 0, 1,
1808 | 1, 0
1809 | );
1810 | },
1811 |
1812 | /**
1813 | * Reflect across the y-axis (mirrored)
1814 | * @return Matrix
1815 | */
1816 | reflectY: function() {
1817 | return new $m2x2(
1818 | -1, 0,
1819 | 0, 1
1820 | );
1821 | },
1822 |
1823 | /**
1824 | * Rotates around the origin
1825 | * @param Number deg
1826 | * @return Matrix
1827 | * @link http://www.w3.org/TR/SVG/coords.html#RotationDefined
1828 | */
1829 | rotate: function(deg) {
1830 | //TODO: detect units
1831 | var rad = $.angle.degreeToRadian(deg),
1832 | costheta = Math.cos(rad),
1833 | sintheta = Math.sin(rad);
1834 |
1835 | var a = costheta,
1836 | b = sintheta,
1837 | c = -sintheta,
1838 | d = costheta;
1839 |
1840 | return new $m2x2(
1841 | a, c,
1842 | b, d
1843 | );
1844 | },
1845 |
1846 | /**
1847 | * Scale
1848 | * @param Number sx
1849 | * @param Number sy
1850 | * @return Matrix
1851 | * @link http://www.w3.org/TR/SVG/coords.html#ScalingDefined
1852 | */
1853 | scale: function (sx, sy) {
1854 | sx = sx || sx === 0 ? sx : 1;
1855 | sy = sy || sy === 0 ? sy : sx;
1856 |
1857 | return new $m2x2(
1858 | sx, 0,
1859 | 0, sy
1860 | );
1861 | },
1862 |
1863 | /**
1864 | * Scale on the X-axis
1865 | * @param Number sx
1866 | * @return Matrix
1867 | */
1868 | scaleX: function (sx) {
1869 | return $m.scale(sx, 1);
1870 | },
1871 |
1872 | /**
1873 | * Scale on the Y-axis
1874 | * @param Number sy
1875 | * @return Matrix
1876 | */
1877 | scaleY: function (sy) {
1878 | return $m.scale(1, sy);
1879 | },
1880 |
1881 | /**
1882 | * Skews on the X-axis and Y-axis
1883 | * @param Number degX
1884 | * @param Number degY
1885 | * @return Matrix
1886 | */
1887 | skew: function (degX, degY) {
1888 | degX = degX || 0;
1889 | degY = degY || 0;
1890 |
1891 | //TODO: detect units
1892 | var radX = $.angle.degreeToRadian(degX),
1893 | radY = $.angle.degreeToRadian(degY),
1894 | x = Math.tan(radX),
1895 | y = Math.tan(radY);
1896 |
1897 | return new $m2x2(
1898 | 1, x,
1899 | y, 1
1900 | );
1901 | },
1902 |
1903 | /**
1904 | * Skews on the X-axis
1905 | * @param Number degX
1906 | * @return Matrix
1907 | * @link http://www.w3.org/TR/SVG/coords.html#SkewXDefined
1908 | */
1909 | skewX: function (degX) {
1910 | return $m.skew(degX);
1911 | },
1912 |
1913 | /**
1914 | * Skews on the Y-axis
1915 | * @param Number degY
1916 | * @return Matrix
1917 | * @link http://www.w3.org/TR/SVG/coords.html#SkewYDefined
1918 | */
1919 | skewY: function (degY) {
1920 | return $m.skew(0, degY);
1921 | },
1922 |
1923 | /**
1924 | * Translate
1925 | * @param Number tx
1926 | * @param Number ty
1927 | * @return Matrix
1928 | * @link http://www.w3.org/TR/SVG/coords.html#TranslationDefined
1929 | */
1930 | translate: function (tx, ty) {
1931 | tx = tx || 0;
1932 | ty = ty || 0;
1933 |
1934 | return new $m3x3(
1935 | 1, 0, tx,
1936 | 0, 1, ty,
1937 | 0, 0, 1
1938 | );
1939 | },
1940 |
1941 | /**
1942 | * Translate on the X-axis
1943 | * @param Number tx
1944 | * @return Matrix
1945 | * @link http://www.w3.org/TR/SVG/coords.html#TranslationDefined
1946 | */
1947 | translateX: function (tx) {
1948 | return $m.translate(tx);
1949 | },
1950 |
1951 | /**
1952 | * Translate on the Y-axis
1953 | * @param Number ty
1954 | * @return Matrix
1955 | * @link http://www.w3.org/TR/SVG/coords.html#TranslationDefined
1956 | */
1957 | translateY: function (ty) {
1958 | return $m.translate(0, ty);
1959 | }
1960 | });
1961 | })(jQuery, this, this.document);
--------------------------------------------------------------------------------