├── .gitignore ├── lib ├── assets │ └── javascripts │ │ └── zui53 │ │ ├── .DS_Store │ │ ├── index.js │ │ ├── surfaces │ │ ├── svg_surface.js.coffee │ │ ├── canvas_surface.js.coffee │ │ └── css_surface.js.coffee │ │ ├── helper.js │ │ ├── tools │ │ ├── toolset.js.coffee │ │ ├── pan_tool.js.coffee │ │ └── zoom_tool.js.coffee │ │ └── zui53.js.coffee ├── zui53.rb └── generators │ └── zui53 │ └── install_generator.rb ├── Gemfile ├── README.markdown ├── Rakefile ├── zui53.gemspec ├── demos ├── css_surface.html └── svg_surface.html ├── LICENSE └── vendor └── assets └── javascripts ├── jquery.mousewheel.js ├── sylvester.js └── jquery.transform.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | build 6 | -------------------------------------------------------------------------------- /lib/assets/javascripts/zui53/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianguenther/zui53/HEAD/lib/assets/javascripts/zui53/.DS_Store -------------------------------------------------------------------------------- /lib/zui53.rb: -------------------------------------------------------------------------------- 1 | module Zui53 2 | if ::Rails.version >= "3.1" 3 | class Engine < ::Rails::Engine 4 | 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/assets/javascripts/zui53/index.js: -------------------------------------------------------------------------------- 1 | //= require sylvester 2 | //= require jquery.transform 3 | //= require jquery.mousewheel 4 | //= require ./zui53 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in zui53.gemspec 4 | gemspec 5 | 6 | gem 'coffee-script' 7 | # gem 'uglifier' 8 | 9 | gem 'sprockets', '>=2.0.0' 10 | -------------------------------------------------------------------------------- /lib/assets/javascripts/zui53/surfaces/svg_surface.js.coffee: -------------------------------------------------------------------------------- 1 | namespace 'ZUI53.Surfaces', (exports)-> 2 | class exports.SVG 3 | constructor: (@node)-> 4 | # console.log @node 5 | # $(@node).css({ 6 | # 'position': 'absolute', 7 | # 'width': '100%', 8 | # 'height': '100%' 9 | # }) 10 | 11 | limits: ()-> 12 | [0.0001, 20000] #20000 13 | 14 | apply: (panX, panY, scale)=> 15 | singleSVG = "translate(#{panX}, #{panY}) scale(#{scale}, #{scale})" 16 | $(@node).attr("transform", singleSVG) -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # NO LONGER MAINTAINED!!! 2 | 3 | 4 | ZUI53 is a JavaScript Library to create powerfull webbased Zoomable User Interfaces (ZUIs) with new technologies like HTML5 and CSS3. 5 | 6 | 7 | 8 | # Build 9 | The Library is written in CoffeeScript. It uses Sprockets to handle dependencies and to generate the build output. 10 | 11 | bundle install 12 | rake 13 | 14 | This will generate 'build/zui53.js' 15 | 16 | # License 17 | MIT Licence, see LICENSE file 18 | 19 | # Contribution 20 | Please feel free to fork and contribute! 21 | 22 | Merge-Requests are welcome! 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | puts 'Init Bundler ...' 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks 4 | 5 | # Set up gems listed in the Gemfile. 6 | ENV['BUNDLE_GEMFILE'] ||= File.join(File.dirname(__FILE__), 'Gemfile') 7 | require 'bundler/setup' 8 | require 'sprockets' 9 | require 'fileutils' 10 | 11 | task :default => [:core] do 12 | 13 | end 14 | 15 | task :core do 16 | puts "Building zui53" 17 | FileUtils.mkdir 'build' unless File.exist? 'build' 18 | assets = Sprockets::Environment.new() do |env| 19 | env.logger = Logger.new(STDOUT) 20 | end 21 | 22 | assets.append_path "lib/assets/javascripts/zui53" 23 | assets.append_path "vendor/assets/javascripts" 24 | 25 | open("build/zui53.js", "w").write( assets["index"] ) 26 | end 27 | -------------------------------------------------------------------------------- /zui53.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "zui53" 6 | s.version = "0.0.4" 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Florian Günther"] 9 | s.email = ["mail@gee-f.de"] 10 | s.homepage = "" 11 | s.summary = %q{ZUI JavaScript Library} 12 | s.description = %q{ZUI53 is a JavaScript Library to create powerfull webbased Zoomable User Interfaces (ZUIs) with new technologies like HTML5 and CSS3.} 13 | 14 | # s.rubyforge_project = "zui53" 15 | 16 | s.files = `git ls-files`.split("\n") 17 | # s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | # s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | end 21 | -------------------------------------------------------------------------------- /lib/assets/javascripts/zui53/surfaces/canvas_surface.js.coffee: -------------------------------------------------------------------------------- 1 | namespace 'ZUI53.Surfaces', (exports)-> 2 | class exports.Canvas 3 | constructor : (id = 'canvas', @render)-> 4 | @canvas = document.getElementById(id) 5 | @parent = $(@canvas).parent() 6 | @ctx = @canvas.getContext('2d') 7 | 8 | $(window).resize @resize 9 | @panX = @panY = 0 10 | @scale = 1 11 | @resize() 12 | 13 | resize : ()=> 14 | @w = @parent.width() 15 | @h = @parent.height() 16 | @canvas.width = @w 17 | @canvas.height = @h 18 | @apply(@panX, @panY, @scale) 19 | 20 | limits : -> 21 | null 22 | 23 | apply : (@panX, @panY, @scale)=> 24 | @ctx.clearRect(0, 0, @w, @h) 25 | @ctx.save() 26 | @ctx.translate(@panX, @panY) 27 | @ctx.scale(@scale, @scale) 28 | @render(@ctx) 29 | @ctx.restore() 30 | -------------------------------------------------------------------------------- /lib/generators/zui53/install_generator.rb: -------------------------------------------------------------------------------- 1 | module Zui53 2 | class InstallGenerator < Rails::Generators::Base 3 | desc "Copies zui53 assets" 4 | source_root File.expand_path('../../../../build', __FILE__) 5 | 6 | def copy_stylesheets_and_images 7 | copy_file "zui53.js", "app/assets/javascripts/zui53.js" 8 | 9 | # ["zui53.js.coffee"].each do |f| 10 | # copy_file "javascripts/zui53/#{f}", "app/assets/javascripts/#{f}" 11 | # end 12 | # copy_file "images/css3buttons/css3-github-buttons-icons.png", 13 | # "public/images/css3buttons/css3-github-buttons-icons.png" 14 | # copy_file "stylesheets/css3buttons/css3-github-buttons.css", 15 | # "public/stylesheets/css3buttons/css3-github-buttons.css" 16 | # copy_file "stylesheets/css3buttons/reset.css", 17 | # "public/stylesheets/css3buttons/reset.css" 18 | # gsub_file "public/stylesheets/css3buttons/css3-github-buttons.css", /url\(css3buttons/, "url(/images/css3buttons" 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/assets/javascripts/zui53/surfaces/css_surface.js.coffee: -------------------------------------------------------------------------------- 1 | namespace 'ZUI53.Surfaces', (exports)-> 2 | class exports.CSS 3 | constructor: (@node)-> 4 | $(@node).transform({origin:['0','0']}) 5 | $(@node).css({ 6 | # '-webkit-transform-origin': '0 0', 7 | # '-moz-transform-origin': '0 0', 8 | # '-o-transform-origin': '0 0', 9 | # 'transform-origin': '0 0', 10 | 'position': 'absolute' 11 | }) 12 | 13 | limits: ()-> 14 | null 15 | 16 | apply: (panX, panY, scale)=> 17 | $(@node).transform({matrix: [scale,0.0,0.0,scale,panX,panY]}); 18 | # matrix = "matrix(#{scale}, 0.0, 0.0, #{scale}, #{panX}, #{panY})" 19 | # single = "translate(#{pX}px, #{pY}px) scale(#{scale}, #{scale})" 20 | 21 | # $(@node).css("-webkit-transform", matrix) 22 | # $(@node).css({ 23 | # # "-moz-transform": matrix, 24 | # # "-o-transform": matrix, 25 | # # "transform": matrix, 26 | # "-webkit-transform": matrix 27 | # }) -------------------------------------------------------------------------------- /demos/css_surface.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 |
Hello World
26 |
27 |
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 | 25 | 26 | 27 | 28 | 29 | 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); --------------------------------------------------------------------------------