├── LICENSE ├── assets ├── reset.css └── styles.css ├── index.html ├── lib └── mootools-1.2.4-core-yc.js └── source ├── canvas.js ├── cloth.js ├── constraint.js ├── fast_vector.js └── point.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andrew Hoyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, font, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | dl, dt, dd, ol, ul, li, 7 | fieldset, form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td { 9 | margin: 0; 10 | padding: 0; 11 | border: 0; 12 | outline: 0; 13 | font-weight: inherit; 14 | font-style: inherit; 15 | font-size: 100%; 16 | font-family: inherit; 17 | vertical-align: baseline; 18 | } 19 | 20 | body { 21 | line-height: 1; 22 | color: black; 23 | background: white; 24 | } 25 | 26 | ol, ul { 27 | list-style: none; 28 | } 29 | 30 | table { 31 | border-collapse: separate; 32 | border-spacing: 0; 33 | vertical-align: middle; 34 | } 35 | 36 | caption, th, td { 37 | text-align: left; 38 | font-weight: normal; 39 | vertical-align: middle; 40 | } 41 | 42 | q, blockquote { 43 | quotes: "" ""; 44 | } 45 | 46 | q:before, q:after, blockquote:before, blockquote:after { 47 | content: ""; 48 | } 49 | 50 | a img { 51 | border: none; 52 | } 53 | -------------------------------------------------------------------------------- /assets/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: lucida grande, lucida, sans-serif; 3 | color: #555; 4 | text-shadow: 0 1px 0px #eee; 5 | line-height: 1.4em; 6 | font-size: 13px; 7 | padding: 20px; 8 | width: 990px; 9 | margin: 0 auto; 10 | letter-spacing: -0.07em; 11 | } 12 | 13 | h1, h3 { 14 | font-weight: normal; 15 | } 16 | 17 | h1, h3 { 18 | margin: 0 0 0.8em 0; 19 | } 20 | 21 | h1 { font-size: 2em; } 22 | h2 { font-size: 1.75em; } 23 | h3 { font-size: 1.5em; } 24 | h4 { font-size: 1.25em; } 25 | h5 { font-size: 1em; } 26 | h6 { font-size: 0.75em; } 27 | 28 | canvas { 29 | border: 1px solid #ddd; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | div.left-column { 35 | width: 275px; 36 | float: left; 37 | } 38 | 39 | div.right-column { 40 | margin-left: 275px; 41 | padding: 0 20px; 42 | width: 675px; 43 | } 44 | 45 | nav, div.chrome-link, div.experiment, div.social-links { 46 | text-align: center; 47 | } 48 | 49 | nav { 50 | margin: 0.5em 0 1em; 51 | } 52 | 53 | p { 54 | text-indent: 1em; 55 | margin-bottom: 1em; 56 | } 57 | 58 | a, a:visited { 59 | color: #db98d7; 60 | } 61 | 62 | li { 63 | margin: 15px; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Andrew Hoyer | The Cloth Simulation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |

The Cloth Simulation

25 | 26 | 29 | 30 |

Every line in the cloth simulation is technically called a constraint and every point is a point mass (an object with no dimension, just location and mass). All the constraints do is control the distance between each point mass. If two points move too far apart, it will pull them closer. If two points are too close together, it will push them apart. The cloth is really then just a collection of constraints and point masses in a never ending struggle.

31 | 32 | 36 | 37 | 40 |
41 |
42 | 43 |
44 | 45 | Sorry, It looks as though your browser does not support the canvas tag... If you can, I suggest that you try Chrome or Safari. 46 | 47 |

Click and drag to move points. Hold down any key to pin them.

48 |

Draw Lines   Draw Points

49 |
50 | 51 |
52 |

A little more detail:

53 |

What makes this simulation special is the speed at which everything is computed. Javascript (the language this is written in) is not exactly the most efficient language for this type of computation. This being said, much time was spent squeezing out every little detail that slows things down.

54 | 55 |

The most computationally expensive part is trying to satisfy the constraints. To do this requires the calculation of distance between two points. This is easy to do with a little math, but that often involves an expensive square root. This is something that cannot simply be thrown out either, so what do you do? You approximate it. There are lots of mathematical tools for approximating functions, in this case I chose the first couple terms of a taylor expansion.

56 | 57 |

"Boring!" you say.

58 | 59 |

No. Not boring. Beautiful...

60 | 61 |

Maybe a little more detail:

62 | 63 |

Another pretty neat thing about this simulation is how all the constraints are satisfied. As I mentioned above, a constraint is basically a rule that controls the distance between two points. So for example if a point has moved too far away from its constrained counterpart, the constraint will suck it back in. What makes this a little trickier is the fact that we have several constraints attached to a single point. This means that this point is going to be constantly jerked around when the constraint satisfaction process begins to execute. In terms of visuals, the cloth would become really springy, jittery and all around unnatural looking.

64 | 65 |

As it turns out there is a really simple solution to this problem. Instead of satisfying all the constraints just once, you simply satisfy them several times before updating the screen. In the case of this cloth simulation all I needed to do was try satisfying the constraints twice. But for things like simple rope simulations it may be necessary to satisfy several times (maybe 4 or 5). The more times you satisfy, the more rigid the constraint becomes. This process is known as relaxation and is pretty darn cool.

66 | 67 |

Knowledge is power:

68 | 69 |

If you're interested here are some links:

70 | 71 | 76 |
77 | 78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /lib/mootools-1.2.4-core-yc.js: -------------------------------------------------------------------------------- 1 | //MooTools, , My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2009 Valerio Proietti, , MIT Style License. 2 | 3 | var MooTools={version:"1.2.4",build:"0d9113241a90b9cd5643b926795852a2026710d4"};var Native=function(k){k=k||{};var a=k.name;var i=k.legacy;var b=k.protect; 4 | var c=k.implement;var h=k.generics;var f=k.initialize;var g=k.afterImplement||function(){};var d=f||i;h=h!==false;d.constructor=Native;d.$family={name:"native"}; 5 | if(i&&f){d.prototype=i.prototype;}d.prototype.constructor=d;if(a){var e=a.toLowerCase();d.prototype.$family={name:e};Native.typize(d,e);}var j=function(n,l,o,m){if(!b||m||!n.prototype[l]){n.prototype[l]=o; 6 | }if(h){Native.genericize(n,l,b);}g.call(n,l,o);return n;};d.alias=function(n,l,p){if(typeof n=="string"){var o=this.prototype[n];if((n=o)){return j(this,l,n,p); 7 | }}for(var m in n){this.alias(m,n[m],l);}return this;};d.implement=function(m,l,o){if(typeof m=="string"){return j(this,m,l,o);}for(var n in m){j(this,n,m[n],l); 8 | }return this;};if(c){d.implement(c);}return d;};Native.genericize=function(b,c,a){if((!a||!b[c])&&typeof b.prototype[c]=="function"){b[c]=function(){var d=Array.prototype.slice.call(arguments); 9 | return b.prototype[c].apply(d.shift(),d);};}};Native.implement=function(d,c){for(var b=0,a=d.length;b-1:this.indexOf(a)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim(); 66 | },camelCase:function(){return this.replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase()); 67 | });},capitalize:function(){return this.replace(/\b[a-z]/g,function(a){return a.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1"); 68 | },toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(b){var a=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); 69 | return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHex(b):null;},stripScripts:function(b){var a=""; 70 | var c=this.replace(/]*>([\s\S]*?)<\/script>/gi,function(){a+=arguments[1]+"\n";return"";});if(b===true){$exec(a);}else{if($type(b)=="function"){b(a,c); 71 | }}return c;},substitute:function(a,b){return this.replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);}return(a[c]!=undefined)?a[c]:""; 72 | });}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(b){for(var a in this){if(this.hasOwnProperty(a)&&this[a]===b){return a;}}return null; 73 | },hasValue:function(a){return(Hash.keyOf(this,a)!==null);},extend:function(a){Hash.each(a||{},function(c,b){Hash.set(this,b,c);},this);return this;},combine:function(a){Hash.each(a||{},function(c,b){Hash.include(this,b,c); 74 | },this);return this;},erase:function(a){if(this.hasOwnProperty(a)){delete this[a];}return this;},get:function(a){return(this.hasOwnProperty(a))?this[a]:null; 75 | },set:function(a,b){if(!this[a]||this.hasOwnProperty(a)){this[a]=b;}return this;},empty:function(){Hash.each(this,function(b,a){delete this[a];},this); 76 | return this;},include:function(a,b){if(this[a]==undefined){this[a]=b;}return this;},map:function(b,c){var a=new Hash;Hash.each(this,function(e,d){a.set(d,b.call(c,e,d,this)); 77 | },this);return a;},filter:function(b,c){var a=new Hash;Hash.each(this,function(e,d){if(b.call(c,e,d,this)){a.set(d,e);}},this);return a;},every:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&!b.call(c,this[a],a)){return false; 78 | }}return true;},some:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&b.call(c,this[a],a)){return true;}}return false;},getKeys:function(){var a=[]; 79 | Hash.each(this,function(c,b){a.push(b);});return a;},getValues:function(){var a=[];Hash.each(this,function(b){a.push(b);});return a;},toQueryString:function(a){var b=[]; 80 | Hash.each(this,function(f,e){if(a){e=a+"["+e+"]";}var d;switch($type(f)){case"object":d=Hash.toQueryString(f,e);break;case"array":var c={};f.each(function(h,g){c[g]=h; 81 | });d=Hash.toQueryString(c,e);break;default:d=e+"="+encodeURIComponent(f);}if(f!=undefined){b.push(d);}});return b.join("&");}});Hash.alias({keyOf:"indexOf",hasValue:"contains"}); 82 | var Event=new Native({name:"Event",initialize:function(a,f){f=f||window;var k=f.document;a=a||f.event;if(a.$extended){return a;}this.$extended=true;var j=a.type; 83 | var g=a.target||a.srcElement;while(g&&g.nodeType==3){g=g.parentNode;}if(j.test(/key/)){var b=a.which||a.keyCode;var m=Event.Keys.keyOf(b);if(j=="keydown"){var d=b-111; 84 | if(d>0&&d<13){m="f"+d;}}m=m||String.fromCharCode(b).toLowerCase();}else{if(j.match(/(click|mouse|menu)/i)){k=(!k.compatMode||k.compatMode=="CSS1Compat")?k.html:k.body; 85 | var i={x:a.pageX||a.clientX+k.scrollLeft,y:a.pageY||a.clientY+k.scrollTop};var c={x:(a.pageX)?a.pageX-f.pageXOffset:a.clientX,y:(a.pageY)?a.pageY-f.pageYOffset:a.clientY}; 86 | if(j.match(/DOMMouseScroll|mousewheel/)){var h=(a.wheelDelta)?a.wheelDelta/120:-(a.detail||0)/3;}var e=(a.which==3)||(a.button==2);var l=null;if(j.match(/over|out/)){switch(j){case"mouseover":l=a.relatedTarget||a.fromElement; 87 | break;case"mouseout":l=a.relatedTarget||a.toElement;}if(!(function(){while(l&&l.nodeType==3){l=l.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){l=false; 88 | }}}}return $extend(this,{event:a,type:j,page:i,client:c,rightClick:e,wheel:h,relatedTarget:l,target:g,code:b,key:m,shift:a.shiftKey,control:a.ctrlKey,alt:a.altKey,meta:a.metaKey}); 89 | }});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault(); 90 | },stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault(); 91 | }else{this.event.returnValue=false;}return this;}});function Class(b){if(b instanceof Function){b={initialize:b};}var a=function(){Object.reset(this);if(a._prototyping){return this; 92 | }this._current=$empty;var c=(this.initialize)?this.initialize.apply(this,arguments):this;delete this._current;delete this.caller;return c;}.extend(this); 93 | a.implement(b);a.constructor=Class;a.prototype.constructor=a;return a;}Function.prototype.protect=function(){this._protected=true;return this;};Object.reset=function(a,c){if(c==null){for(var e in a){Object.reset(a,e); 94 | }return a;}delete a[c];switch($type(a[c])){case"object":var d=function(){};d.prototype=a[c];var b=new d;a[c]=Object.reset(b);break;case"array":a[c]=$unlink(a[c]); 95 | break;}return a;};new Native({name:"Class",initialize:Class}).extend({instantiate:function(b){b._prototyping=true;var a=new b;delete b._prototyping;return a; 96 | },wrap:function(a,b,c){if(c._origin){c=c._origin;}return function(){if(c._protected&&this._current==null){throw new Error('The method "'+b+'" cannot be called.'); 97 | }var e=this.caller,f=this._current;this.caller=f;this._current=arguments.callee;var d=c.apply(this,arguments);this._current=f;this.caller=e;return d;}.extend({_owner:a,_origin:c,_name:b}); 98 | }});Class.implement({implement:function(a,d){if($type(a)=="object"){for(var e in a){this.implement(e,a[e]);}return this;}var f=Class.Mutators[a];if(f){d=f.call(this,d); 99 | if(d==null){return this;}}var c=this.prototype;switch($type(d)){case"function":if(d._hidden){return this;}c[a]=Class.wrap(this,a,d);break;case"object":var b=c[a]; 100 | if($type(b)=="object"){$mixin(b,d);}else{c[a]=$unlink(d);}break;case"array":c[a]=$unlink(d);break;default:c[a]=d;}return this;}});Class.Mutators={Extends:function(a){this.parent=a; 101 | this.prototype=Class.instantiate(a);this.implement("parent",function(){var b=this.caller._name,c=this.caller._owner.parent.prototype[b];if(!c){throw new Error('The method "'+b+'" has no parent.'); 102 | }return c.apply(this,arguments);}.protect());},Implements:function(a){$splat(a).each(function(b){if(b instanceof Function){b=Class.instantiate(b);}this.implement(b); 103 | },this);}};var Chain=new Class({$chain:[],chain:function(){this.$chain.extend(Array.flatten(arguments));return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false; 104 | },clearChain:function(){this.$chain.empty();return this;}});var Events=new Class({$events:{},addEvent:function(c,b,a){c=Events.removeOn(c);if(b!=$empty){this.$events[c]=this.$events[c]||[]; 105 | this.$events[c].include(b);if(a){b.internal=true;}}return this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this;},fireEvent:function(c,b,a){c=Events.removeOn(c); 106 | if(!this.$events||!this.$events[c]){return this;}this.$events[c].each(function(d){d.create({bind:this,delay:a,"arguments":b})();},this);return this;},removeEvent:function(b,a){b=Events.removeOn(b); 107 | if(!this.$events[b]){return this;}if(!a.internal){this.$events[b].erase(a);}return this;},removeEvents:function(c){var d;if($type(c)=="object"){for(d in c){this.removeEvent(d,c[d]); 108 | }return this;}if(c){c=Events.removeOn(c);}for(d in this.$events){if(c&&c!=d){continue;}var b=this.$events[d];for(var a=b.length;a--;a){this.removeEvent(d,b[a]); 109 | }}return this;}});Events.removeOn=function(a){return a.replace(/^on([A-Z])/,function(b,c){return c.toLowerCase();});};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments)); 110 | if(!this.addEvent){return this;}for(var a in this.options){if($type(this.options[a])!="function"||!(/^on[A-Z]/).test(a)){continue;}this.addEvent(a,this.options[a]); 111 | delete this.options[a];}return this;}});var Element=new Native({name:"Element",legacy:window.Element,initialize:function(a,b){var c=Element.Constructors.get(a); 112 | if(c){return c(b);}if(typeof a=="string"){return document.newElement(a,b);}return document.id(a).set(b);},afterImplement:function(a,b){Element.Prototype[a]=b; 113 | if(Array[a]){return;}Elements.implement(a,function(){var c=[],g=true;for(var e=0,d=this.length;e";}return document.id(this.createElement(a)).set(b);},newTextNode:function(a){return this.createTextNode(a); 123 | },getDocument:function(){return this;},getWindow:function(){return this.window;},id:(function(){var a={string:function(d,c,b){d=b.getElementById(d);return(d)?a.element(d,c):null; 124 | },element:function(b,e){$uid(b);if(!e&&!b.$family&&!(/^object|embed$/i).test(b.tagName)){var c=Element.Prototype;for(var d in c){b[d]=c[d];}}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d); 125 | }return null;}};a.textnode=a.whitespace=a.window=a.document=$arguments(0);return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=$type(c);return(a[b])?a[b](c,e,d||document):null; 126 | };})()});if(window.$==null){Window.implement({$:function(a,b){return document.id(a,b,this.document);}});}Window.implement({$$:function(a){if(arguments.length==1&&typeof a=="string"){return this.document.getElements(a); 127 | }var f=[];var c=Array.flatten(arguments);for(var d=0,b=c.length;d1);a.each(function(e){var f=this.getElementsByTagName(e.trim());(b)?c.extend(f):c=f; 130 | },this);return new Elements(c,{ddup:b,cash:!d});}});(function(){var h={},f={};var i={input:"checked",option:"selected",textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerHTML":"value"}; 131 | var c=function(l){return(f[l]||(f[l]={}));};var g=function(n,l){if(!n){return;}var m=n.uid;if(Browser.Engine.trident){if(n.clearAttributes){var q=l&&n.cloneNode(false); 132 | n.clearAttributes();if(q){n.mergeAttributes(q);}}else{if(n.removeEvents){n.removeEvents();}}if((/object/i).test(n.tagName)){for(var o in n){if(typeof n[o]=="function"){n[o]=$empty; 133 | }}Element.dispose(n);}}if(!m){return;}h[m]=f[m]=null;};var d=function(){Hash.each(h,g);if(Browser.Engine.trident){$A(document.getElementsByTagName("object")).each(g); 134 | }if(window.CollectGarbage){CollectGarbage();}h=f=null;};var j=function(n,l,s,m,p,r){var o=n[s||l];var q=[];while(o){if(o.nodeType==1&&(!m||Element.match(o,m))){if(!p){return document.id(o,r); 135 | }q.push(o);}o=o[l];}return(p)?new Elements(q,{ddup:false,cash:!r}):null;};var e={html:"innerHTML","class":"className","for":"htmlFor",defaultValue:"defaultValue",text:(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?"innerText":"textContent"}; 136 | var b=["compact","nowrap","ismap","declare","noshade","checked","disabled","readonly","multiple","selected","noresize","defer"];var k=["value","type","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"]; 137 | b=b.associate(b);Hash.extend(e,b);Hash.extend(e,k.associate(k.map(String.toLowerCase)));var a={before:function(m,l){if(l.parentNode){l.parentNode.insertBefore(m,l); 138 | }},after:function(m,l){if(!l.parentNode){return;}var n=l.nextSibling;(n)?l.parentNode.insertBefore(m,n):l.parentNode.appendChild(m);},bottom:function(m,l){l.appendChild(m); 139 | },top:function(m,l){var n=l.firstChild;(n)?l.insertBefore(m,n):l.appendChild(m);}};a.inside=a.bottom;Hash.each(a,function(l,m){m=m.capitalize();Element.implement("inject"+m,function(n){l(this,document.id(n,true)); 140 | return this;});Element.implement("grab"+m,function(n){l(document.id(n,true),this);return this;});});Element.implement({set:function(o,m){switch($type(o)){case"object":for(var n in o){this.set(n,o[n]); 141 | }break;case"string":var l=Element.Properties.get(o);(l&&l.set)?l.set.apply(this,Array.slice(arguments,1)):this.setProperty(o,m);}return this;},get:function(m){var l=Element.Properties.get(m); 142 | return(l&&l.get)?l.get.apply(this,Array.slice(arguments,1)):this.getProperty(m);},erase:function(m){var l=Element.Properties.get(m);(l&&l.erase)?l.erase.apply(this):this.removeProperty(m); 143 | return this;},setProperty:function(m,n){var l=e[m];if(n==undefined){return this.removeProperty(m);}if(l&&b[m]){n=!!n;}(l)?this[l]=n:this.setAttribute(m,""+n); 144 | return this;},setProperties:function(l){for(var m in l){this.setProperty(m,l[m]);}return this;},getProperty:function(m){var l=e[m];var n=(l)?this[l]:this.getAttribute(m,2); 145 | return(b[m])?!!n:(l)?n:n||null;},getProperties:function(){var l=$A(arguments);return l.map(this.getProperty,this).associate(l);},removeProperty:function(m){var l=e[m]; 146 | (l)?this[l]=(l&&b[m])?false:"":this.removeAttribute(m);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this; 147 | },hasClass:function(l){return this.className.contains(l," ");},addClass:function(l){if(!this.hasClass(l)){this.className=(this.className+" "+l).clean(); 148 | }return this;},removeClass:function(l){this.className=this.className.replace(new RegExp("(^|\\s)"+l+"(?:\\s|$)"),"$1");return this;},toggleClass:function(l){return this.hasClass(l)?this.removeClass(l):this.addClass(l); 149 | },adopt:function(){Array.flatten(arguments).each(function(l){l=document.id(l,true);if(l){this.appendChild(l);}},this);return this;},appendText:function(m,l){return this.grab(this.getDocument().newTextNode(m),l); 150 | },grab:function(m,l){a[l||"bottom"](document.id(m,true),this);return this;},inject:function(m,l){a[l||"bottom"](this,document.id(m,true));return this;},replaces:function(l){l=document.id(l,true); 151 | l.parentNode.replaceChild(this,l);return this;},wraps:function(m,l){m=document.id(m,true);return this.replaces(m).grab(m,l);},getPrevious:function(l,m){return j(this,"previousSibling",null,l,false,m); 152 | },getAllPrevious:function(l,m){return j(this,"previousSibling",null,l,true,m);},getNext:function(l,m){return j(this,"nextSibling",null,l,false,m);},getAllNext:function(l,m){return j(this,"nextSibling",null,l,true,m); 153 | },getFirst:function(l,m){return j(this,"nextSibling","firstChild",l,false,m);},getLast:function(l,m){return j(this,"previousSibling","lastChild",l,false,m); 154 | },getParent:function(l,m){return j(this,"parentNode",null,l,false,m);},getParents:function(l,m){return j(this,"parentNode",null,l,true,m);},getSiblings:function(l,m){return this.getParent().getChildren(l,m).erase(this); 155 | },getChildren:function(l,m){return j(this,"nextSibling","firstChild",l,true,m);},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument; 156 | },getElementById:function(o,n){var m=this.ownerDocument.getElementById(o);if(!m){return null;}for(var l=m.parentNode;l!=this;l=l.parentNode){if(!l){return null; 157 | }}return document.id(m,n);},getSelected:function(){return new Elements($A(this.options).filter(function(l){return l.selected;}));},getComputedStyle:function(m){if(this.currentStyle){return this.currentStyle[m.camelCase()]; 158 | }var l=this.getDocument().defaultView.getComputedStyle(this,null);return(l)?l.getPropertyValue([m.hyphenate()]):null;},toQueryString:function(){var l=[]; 159 | this.getElements("input, select, textarea",true).each(function(m){if(!m.name||m.disabled||m.type=="submit"||m.type=="reset"||m.type=="file"){return;}var n=(m.tagName.toLowerCase()=="select")?Element.getSelected(m).map(function(o){return o.value; 160 | }):((m.type=="radio"||m.type=="checkbox")&&!m.checked)?null:m.value;$splat(n).each(function(o){if(typeof o!="undefined"){l.push(m.name+"="+encodeURIComponent(o)); 161 | }});});return l.join("&");},clone:function(o,l){o=o!==false;var r=this.cloneNode(o);var n=function(v,u){if(!l){v.removeAttribute("id");}if(Browser.Engine.trident){v.clearAttributes(); 162 | v.mergeAttributes(u);v.removeAttribute("uid");if(v.options){var w=v.options,s=u.options;for(var t=w.length;t--;){w[t].selected=s[t].selected;}}}var x=i[u.tagName.toLowerCase()]; 163 | if(x&&u[x]){v[x]=u[x];}};if(o){var p=r.getElementsByTagName("*"),q=this.getElementsByTagName("*");for(var m=p.length;m--;){n(p[m],q[m]);}}n(r,this);return document.id(r); 164 | },destroy:function(){Element.empty(this);Element.dispose(this);g(this,true);return null;},empty:function(){$A(this.childNodes).each(function(l){Element.destroy(l); 165 | });return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},hasChild:function(l){l=document.id(l,true);if(!l){return false; 166 | }if(Browser.Engine.webkit&&Browser.Engine.version<420){return $A(this.getElementsByTagName(l.tagName)).contains(l);}return(this.contains)?(this!=l&&this.contains(l)):!!(this.compareDocumentPosition(l)&16); 167 | },match:function(l){return(!l||(l==this)||(Element.get(this,"tag")==l));}});Native.implement([Element,Window,Document],{addListener:function(o,n){if(o=="unload"){var l=n,m=this; 168 | n=function(){m.removeListener("unload",n);l();};}else{h[this.uid]=this;}if(this.addEventListener){this.addEventListener(o,n,false);}else{this.attachEvent("on"+o,n); 169 | }return this;},removeListener:function(m,l){if(this.removeEventListener){this.removeEventListener(m,l,false);}else{this.detachEvent("on"+m,l);}return this; 170 | },retrieve:function(m,l){var o=c(this.uid),n=o[m];if(l!=undefined&&n==undefined){n=o[m]=l;}return $pick(n);},store:function(m,l){var n=c(this.uid);n[m]=l; 171 | return this;},eliminate:function(l){var m=c(this.uid);delete m[l];return this;}});window.addListener("unload",d);})();Element.Properties=new Hash;Element.Properties.style={set:function(a){this.style.cssText=a; 172 | },get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase(); 173 | }};Element.Properties.html=(function(){var c=document.createElement("div");var a={table:[1,"","
"],select:[1,""],tbody:[2,"","
"],tr:[3,"","
"]}; 174 | a.thead=a.tfoot=a.tbody;var b={set:function(){var e=Array.flatten(arguments).join("");var f=Browser.Engine.trident&&a[this.get("tag")];if(f){var g=c;g.innerHTML=f[1]+e+f[2]; 175 | for(var d=f[0];d--;){g=g.firstChild;}this.empty().adopt(g.childNodes);}else{this.innerHTML=e;}}};b.erase=b.set;return b;})();if(Browser.Engine.webkit&&Browser.Engine.version<420){Element.Properties.text={get:function(){if(this.innerText){return this.innerText; 176 | }var a=this.ownerDocument.newElement("div",{html:this.innerHTML}).inject(this.ownerDocument.body);var b=a.innerText;a.destroy();return b;}};}Element.Properties.events={set:function(a){this.addEvents(a); 177 | }};Native.implement([Element,Window,Document],{addEvent:function(e,g){var h=this.retrieve("events",{});h[e]=h[e]||{keys:[],values:[]};if(h[e].keys.contains(g)){return this; 178 | }h[e].keys.push(g);var f=e,a=Element.Events.get(e),c=g,i=this;if(a){if(a.onAdd){a.onAdd.call(this,g);}if(a.condition){c=function(j){if(a.condition.call(this,j)){return g.call(this,j); 179 | }return true;};}f=a.base||f;}var d=function(){return g.call(i);};var b=Element.NativeEvents[f];if(b){if(b==2){d=function(j){j=new Event(j,i.getWindow()); 180 | if(c.call(i,j)===false){j.stop();}};}this.addListener(f,d);}h[e].values.push(d);return this;},removeEvent:function(c,b){var a=this.retrieve("events");if(!a||!a[c]){return this; 181 | }var f=a[c].keys.indexOf(b);if(f==-1){return this;}a[c].keys.splice(f,1);var e=a[c].values.splice(f,1)[0];var d=Element.Events.get(c);if(d){if(d.onRemove){d.onRemove.call(this,b); 182 | }c=d.base||c;}return(Element.NativeEvents[c])?this.removeListener(c,e):this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this; 183 | },removeEvents:function(a){var c;if($type(a)=="object"){for(c in a){this.removeEvent(c,a[c]);}return this;}var b=this.retrieve("events");if(!b){return this; 184 | }if(!a){for(c in b){this.removeEvents(c);}this.eliminate("events");}else{if(b[a]){while(b[a].keys[0]){this.removeEvent(a,b[a].keys[0]);}b[a]=null;}}return this; 185 | },fireEvent:function(d,b,a){var c=this.retrieve("events");if(!c||!c[d]){return this;}c[d].keys.each(function(e){e.create({bind:this,delay:a,"arguments":b})(); 186 | },this);return this;},cloneEvents:function(d,a){d=document.id(d);var c=d.retrieve("events");if(!c){return this;}if(!a){for(var b in c){this.cloneEvents(d,b); 187 | }}else{if(c[a]){c[a].keys.each(function(e){this.addEvent(a,e);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1}; 188 | (function(){var a=function(b){var c=b.relatedTarget;if(c==undefined){return true;}if(c===false){return false;}return($type(this)!="document"&&c!=this&&c.prefix!="xul"&&!this.hasChild(c)); 189 | };Element.Events=new Hash({mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.Engine.gecko)?"DOMMouseScroll":"mousewheel"}}); 190 | })();Element.Properties.styles={set:function(a){this.setStyles(a);}};Element.Properties.opacity={set:function(a,b){if(!b){if(a==0){if(this.style.visibility!="hidden"){this.style.visibility="hidden"; 191 | }}else{if(this.style.visibility!="visible"){this.style.visibility="visible";}}}if(!this.currentStyle||!this.currentStyle.hasLayout){this.style.zoom=1;}if(Browser.Engine.trident){this.style.filter=(a==1)?"":"alpha(opacity="+a*100+")"; 192 | }this.style.opacity=a;this.store("opacity",a);},get:function(){return this.retrieve("opacity",1);}};Element.implement({setOpacity:function(a){return this.set("opacity",a,true); 193 | },getOpacity:function(){return this.get("opacity");},setStyle:function(b,a){switch(b){case"opacity":return this.set("opacity",parseFloat(a));case"float":b=(Browser.Engine.trident)?"styleFloat":"cssFloat"; 194 | }b=b.camelCase();if($type(a)!="string"){var c=(Element.Styles.get(b)||"@").split(" ");a=$splat(a).map(function(e,d){if(!c[d]){return"";}return($type(e)=="number")?c[d].replace("@",Math.round(e)):e; 195 | }).join(" ");}else{if(a==String(Number(a))){a=Math.round(a);}}this.style[b]=a;return this;},getStyle:function(g){switch(g){case"opacity":return this.get("opacity"); 196 | case"float":g=(Browser.Engine.trident)?"styleFloat":"cssFloat";}g=g.camelCase();var a=this.style[g];if(!$chk(a)){a=[];for(var f in Element.ShortStyles){if(g!=f){continue; 197 | }for(var e in Element.ShortStyles[f]){a.push(this.getStyle(e));}return a.join(" ");}a=this.getComputedStyle(g);}if(a){a=String(a);var c=a.match(/rgba?\([\d\s,]+\)/); 198 | if(c){a=a.replace(c[0],c[0].rgbToHex());}}if(Browser.Engine.presto||(Browser.Engine.trident&&!$chk(parseInt(a,10)))){if(g.test(/^(height|width)$/)){var b=(g=="width")?["left","right"]:["top","bottom"],d=0; 199 | b.each(function(h){d+=this.getStyle("border-"+h+"-width").toInt()+this.getStyle("padding-"+h).toInt();},this);return this["offset"+g.capitalize()]-d+"px"; 200 | }if((Browser.Engine.presto)&&String(a).test("px")){return a;}if(g.test(/(border(.+)Width|margin|padding)/)){return"0px";}}return a;},setStyles:function(b){for(var a in b){this.setStyle(a,b[a]); 201 | }return this;},getStyles:function(){var a={};Array.flatten(arguments).each(function(b){a[b]=this.getStyle(b);},this);return a;}});Element.Styles=new Hash({left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"}); 202 | Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(g){var f=Element.ShortStyles; 203 | var b=Element.Styles;["margin","padding"].each(function(h){var i=h+g;f[h][i]=b[i]="@px";});var e="border"+g;f.border[e]=b[e]="@px @ rgb(@, @, @)";var d=e+"Width",a=e+"Style",c=e+"Color"; 204 | f[e]={};f.borderWidth[d]=f[e][d]=b[d]="@px";f.borderStyle[a]=f[e][a]=b[a]="@";f.borderColor[c]=f[e][c]=b[c]="rgb(@, @, @)";});(function(){Element.implement({scrollTo:function(h,i){if(b(this)){this.getWindow().scrollTo(h,i); 205 | }else{this.scrollLeft=h;this.scrollTop=i;}return this;},getSize:function(){if(b(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight}; 206 | },getScrollSize:function(){if(b(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(b(this)){return this.getWindow().getScroll(); 207 | }return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var i=this,h={x:0,y:0};while(i&&!b(i)){h.x+=i.scrollLeft;h.y+=i.scrollTop;i=i.parentNode; 208 | }return h;},getOffsetParent:function(){var h=this;if(b(h)){return null;}if(!Browser.Engine.trident){return h.offsetParent;}while((h=h.parentNode)&&!b(h)){if(d(h,"position")!="static"){return h; 209 | }}return null;},getOffsets:function(){if(this.getBoundingClientRect){var j=this.getBoundingClientRect(),m=document.id(this.getDocument().documentElement),p=m.getScroll(),k=this.getScrolls(),i=this.getScroll(),h=(d(this,"position")=="fixed"); 210 | return{x:j.left.toInt()+k.x-i.x+((h)?0:p.x)-m.clientLeft,y:j.top.toInt()+k.y-i.y+((h)?0:p.y)-m.clientTop};}var l=this,n={x:0,y:0};if(b(this)){return n; 211 | }while(l&&!b(l)){n.x+=l.offsetLeft;n.y+=l.offsetTop;if(Browser.Engine.gecko){if(!f(l)){n.x+=c(l);n.y+=g(l);}var o=l.parentNode;if(o&&d(o,"overflow")!="visible"){n.x+=c(o); 212 | n.y+=g(o);}}else{if(l!=this&&Browser.Engine.webkit){n.x+=c(l);n.y+=g(l);}}l=l.offsetParent;}if(Browser.Engine.gecko&&!f(this)){n.x-=c(this);n.y-=g(this); 213 | }return n;},getPosition:function(k){if(b(this)){return{x:0,y:0};}var l=this.getOffsets(),i=this.getScrolls();var h={x:l.x-i.x,y:l.y-i.y};var j=(k&&(k=document.id(k)))?k.getPosition():{x:0,y:0}; 214 | return{x:h.x-j.x,y:h.y-j.y};},getCoordinates:function(j){if(b(this)){return this.getWindow().getCoordinates();}var h=this.getPosition(j),i=this.getSize(); 215 | var k={left:h.x,top:h.y,width:i.x,height:i.y};k.right=k.left+k.width;k.bottom=k.top+k.height;return k;},computePosition:function(h){return{left:h.x-e(this,"margin-left"),top:h.y-e(this,"margin-top")}; 216 | },setPosition:function(h){return this.setStyles(this.computePosition(h));}});Native.implement([Document,Window],{getSize:function(){if(Browser.Engine.presto||Browser.Engine.webkit){var i=this.getWindow(); 217 | return{x:i.innerWidth,y:i.innerHeight};}var h=a(this);return{x:h.clientWidth,y:h.clientHeight};},getScroll:function(){var i=this.getWindow(),h=a(this); 218 | return{x:i.pageXOffset||h.scrollLeft,y:i.pageYOffset||h.scrollTop};},getScrollSize:function(){var i=a(this),h=this.getSize();return{x:Math.max(i.scrollWidth,h.x),y:Math.max(i.scrollHeight,h.y)}; 219 | },getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var h=this.getSize();return{top:0,left:0,bottom:h.y,right:h.x,height:h.y,width:h.x}; 220 | }});var d=Element.getComputedStyle;function e(h,i){return d(h,i).toInt()||0;}function f(h){return d(h,"-moz-box-sizing")=="border-box";}function g(h){return e(h,"border-top-width"); 221 | }function c(h){return e(h,"border-left-width");}function b(h){return(/^(?:body|html)$/i).test(h.tagName);}function a(h){var i=h.getDocument();return(!i.compatMode||i.compatMode=="CSS1Compat")?i.html:i.body; 222 | }})();Element.alias("setPosition","position");Native.implement([Window,Document,Element],{getHeight:function(){return this.getSize().y;},getWidth:function(){return this.getSize().x; 223 | },getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;},getScrollHeight:function(){return this.getScrollSize().y; 224 | },getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;},getLeft:function(){return this.getPosition().x; 225 | }});Native.implement([Document,Element],{getElements:function(h,g){h=h.split(",");var c,e={};for(var d=0,b=h.length;d1),cash:!g});}});Element.implement({match:function(b){if(!b||(b==this)){return true; 227 | }var d=Selectors.Utils.parseTagAndID(b);var a=d[0],e=d[1];if(!Selectors.Filters.byID(this,e)||!Selectors.Filters.byTag(this,a)){return false;}var c=Selectors.Utils.parseSelector(b); 228 | return(c)?Selectors.Utils.filter(this,c,{}):true;}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)}; 229 | Selectors.Utils={chk:function(b,c){if(!c){return true;}var a=$uid(b);if(!c[a]){return c[a]=true;}return false;},parseNthArgument:function(h){if(Selectors.Cache.nth[h]){return Selectors.Cache.nth[h]; 230 | }var e=h.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!e){return false;}var g=parseInt(e[1],10);var d=(g||g===0)?g:1;var f=e[2]||false;var c=parseInt(e[3],10)||0; 231 | if(d!=0){c--;while(c<1){c+=d;}while(c>=d){c-=d;}}else{d=c;f="index";}switch(f){case"n":e={a:d,b:c,special:"n"};break;case"odd":e={a:2,b:0,special:"n"}; 232 | break;case"even":e={a:2,b:1,special:"n"};break;case"first":e={a:0,special:"index"};break;case"last":e={special:"last-child"};break;case"only":e={special:"only-child"}; 233 | break;default:e={a:(d-1),special:"index"};}return Selectors.Cache.nth[h]=e;},parseSelector:function(e){if(Selectors.Cache.parsed[e]){return Selectors.Cache.parsed[e]; 234 | }var d,h={classes:[],pseudos:[],attributes:[]};while((d=Selectors.RegExps.combined.exec(e))){var i=d[1],g=d[2],f=d[3],b=d[5],c=d[6],j=d[7];if(i){h.classes.push(i); 235 | }else{if(c){var a=Selectors.Pseudo.get(c);if(a){h.pseudos.push({parser:a,argument:j});}else{h.attributes.push({name:c,operator:"=",value:j});}}else{if(g){h.attributes.push({name:g,operator:f,value:b}); 236 | }}}}if(!h.classes.length){delete h.classes;}if(!h.attributes.length){delete h.attributes;}if(!h.pseudos.length){delete h.pseudos;}if(!h.classes&&!h.attributes&&!h.pseudos){h=null; 237 | }return Selectors.Cache.parsed[e]=h;},parseTagAndID:function(b){var a=b.match(Selectors.RegExps.tag);var c=b.match(Selectors.RegExps.id);return[(a)?a[1]:"*",(c)?c[1]:false]; 238 | },filter:function(f,c,e){var d;if(c.classes){for(d=c.classes.length;d--;d){var g=c.classes[d];if(!Selectors.Filters.byClass(f,g)){return false;}}}if(c.attributes){for(d=c.attributes.length; 239 | d--;d){var b=c.attributes[d];if(!Selectors.Filters.byAttribute(f,b.name,b.operator,b.value)){return false;}}}if(c.pseudos){for(d=c.pseudos.length;d--;d){var a=c.pseudos[d]; 240 | if(!Selectors.Filters.byPseudo(f,a.parser,a.argument,e)){return false;}}}return true;},getByTagAndID:function(b,a,d){if(d){var c=(b.getElementById)?b.getElementById(d,true):Element.getElementById(b,d,true); 241 | return(c&&Selectors.Filters.byTag(c,a))?[c]:[];}else{return b.getElementsByTagName(a);}},search:function(o,h,t){var b=[];var c=h.trim().replace(Selectors.RegExps.splitter,function(k,j,i){b.push(j); 242 | return":)"+i;}).split(":)");var p,e,A;for(var z=0,v=c.length;z":function(h,g,j,a,f){var c=Selectors.Utils.getByTagAndID(g,j,a);for(var e=0,d=c.length;ea){return false;}}return(c==a);},even:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n+1",a); 260 | },odd:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n",a);},selected:function(){return this.selected;},enabled:function(){return(this.disabled===false); 261 | }});Element.Events.domready={onAdd:function(a){if(Browser.loaded){a.call(this);}}};(function(){var b=function(){if(Browser.loaded){return;}Browser.loaded=true; 262 | window.fireEvent("domready");document.fireEvent("domready");};window.addEvent("load",b);if(Browser.Engine.trident){var a=document.createElement("div"); 263 | (function(){($try(function(){a.doScroll();return document.id(a).inject(document.body).set("html","temp").dispose();}))?b():arguments.callee.delay(50);})(); 264 | }else{if(Browser.Engine.webkit&&Browser.Engine.version<525){(function(){(["loaded","complete"].contains(document.readyState))?b():arguments.callee.delay(50); 265 | })();}else{document.addEvent("DOMContentLoaded",b);}}})();var JSON=new Hash(this.JSON&&{stringify:JSON.stringify,parse:JSON.parse}).extend({$specialChars:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},$replaceChars:function(a){return JSON.$specialChars[a]||"\\u00"+Math.floor(a.charCodeAt()/16).toString(16)+(a.charCodeAt()%16).toString(16); 266 | },encode:function(b){switch($type(b)){case"string":return'"'+b.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"';case"array":return"["+String(b.map(JSON.encode).clean())+"]"; 267 | case"object":case"hash":var a=[];Hash.each(b,function(e,d){var c=JSON.encode(e);if(c){a.push(JSON.encode(d)+":"+c);}});return"{"+a+"}";case"number":case"boolean":return String(b); 268 | case false:return"null";}return null;},decode:function(string,secure){if($type(string)!="string"||!string.length){return null;}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null; 269 | }return eval("("+string+")");}});Native.implement([Hash,Array,String,Number],{toJSON:function(){return JSON.encode(this);}});var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(b,a){this.key=b; 270 | this.setOptions(a);},write:function(b){b=encodeURIComponent(b);if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path; 271 | }if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure"; 272 | }this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)"); 273 | return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this;}});Cookie.write=function(b,c,a){return new Cookie(b,a).write(c); 274 | };Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();};var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object; 275 | },initialize:function(l,m){this.instance="Swiff_"+$time();this.setOptions(m);m=this.options;var b=this.id=m.id||this.instance;var a=document.id(m.container); 276 | Swiff.CallBacks[this.instance]={};var e=m.params,g=m.vars,f=m.callBacks;var h=$extend({height:m.height,width:m.width},m.properties);var k=this;for(var d in f){Swiff.CallBacks[this.instance][d]=(function(n){return function(){return n.apply(k.object,arguments); 277 | };})(f[d]);g[d]="Swiff.CallBacks."+this.instance+"."+d;}e.flashVars=Hash.toQueryString(g);if(Browser.Engine.trident){h.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"; 278 | e.movie=l;}else{h.type="application/x-shockwave-flash";h.data=l;}var j=''; 279 | }}j+="";this.object=((a)?a.empty():new Element("div")).set("html",j).firstChild;},replaces:function(a){a=document.id(a,true);a.parentNode.replaceChild(this.toElement(),a); 280 | return this;},inject:function(a){document.id(a,true).appendChild(this.toElement());return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].extend(arguments)); 281 | }});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction(''+__flash__argumentsToXML(arguments,2)+""); 282 | return eval(rs);};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore"},initialize:function(a){this.subject=this.subject||this; 283 | this.setOptions(a);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt();var b=this.options.wait;if(b===false){this.options.link="cancel"; 284 | }},getTransition:function(){return function(a){return -(Math.cos(Math.PI*a)-1)/2;};},step:function(){var a=$time();if(a=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2); 325 | break;}}return e;},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,[a+2]); 326 | });});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,noCache:false},initialize:function(a){this.xhr=new Browser.Request(); 327 | this.setOptions(a);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return; 328 | }this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));this.xhr.onreadystatechange=$empty;if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML}; 329 | this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}},isSuccess:function(){return((this.status>=200)&&(this.status<300)); 330 | },processScripts:function(a){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(a);}return a.stripScripts(this.options.evalScripts); 331 | },success:function(b,a){this.onSuccess(this.processScripts(b),a);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain(); 332 | },failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},setHeader:function(a,b){this.headers.set(a,b); 333 | return this;},getHeader:function(a){return $try(function(){return this.xhr.getResponseHeader(a);}.bind(this));},check:function(){if(!this.running){return true; 334 | }switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false;}return false;},send:function(k){if(!this.check(k)){return this; 335 | }this.running=true;var i=$type(k);if(i=="string"||i=="element"){k={data:k};}var d=this.options;k=$extend({data:d.data,url:d.url,method:d.method},k);var g=k.data,b=String(k.url),a=k.method.toLowerCase(); 336 | switch($type(g)){case"element":g=document.id(g).toQueryString();break;case"object":case"hash":g=Hash.toQueryString(g);}if(this.options.format){var j="format="+this.options.format; 337 | g=(g)?j+"&"+g:j;}if(this.options.emulation&&!["get","post"].contains(a)){var h="_method="+a;g=(g)?h+"&"+g:h;a="post";}if(this.options.urlEncoded&&a=="post"){var c=(this.options.encoding)?"; charset="+this.options.encoding:""; 338 | this.headers.set("Content-type","application/x-www-form-urlencoded"+c);}if(this.options.noCache){var f="noCache="+new Date().getTime();g=(g)?f+"&"+g:f; 339 | }var e=b.lastIndexOf("/");if(e>-1&&(e=b.indexOf("#"))>-1){b=b.substr(0,e);}if(g&&a=="get"){b=b+(b.contains("?")?"&":"?")+g;g=null;}this.xhr.open(a.toUpperCase(),b,this.options.async); 340 | this.xhr.onreadystatechange=this.onStateChange.bind(this);this.headers.each(function(m,l){try{this.xhr.setRequestHeader(l,m);}catch(n){this.fireEvent("exception",[l,m]); 341 | }},this);this.fireEvent("request");this.xhr.send(g);if(!this.options.async){this.onStateChange();}return this;},cancel:function(){if(!this.running){return this; 342 | }this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});(function(){var a={}; 343 | ["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(b){a[b]=function(){var c=Array.link(arguments,{url:String.type,data:$defined}); 344 | return this.send($extend(c,{method:b}));};});Request.implement(a);})();Element.Properties.send={set:function(a){var b=this.retrieve("send");if(b){b.cancel(); 345 | }return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")},a));},get:function(a){if(a||!this.retrieve("send")){if(a||!this.retrieve("send:options")){this.set("send",a); 346 | }this.store("send",new Request(this.retrieve("send:options")));}return this.retrieve("send");}};Element.implement({send:function(a){var b=this.get("send"); 347 | b.send({data:this,url:a||b.options.url});return this;}});Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false},processHTML:function(c){var b=c.match(/]*>([\s\S]*?)<\/body>/i); 348 | c=(b)?b[1]:c;var a=new Element("div");return $try(function(){var d=""+c+"",g;if(Browser.Engine.trident){g=new ActiveXObject("Microsoft.XMLDOM"); 349 | g.async=false;g.loadXML(d);}else{g=new DOMParser().parseFromString(d,"text/xml");}d=g.getElementsByTagName("root")[0];if(!d){return null;}for(var f=0,e=d.childNodes.length; 350 | f lx && px < lx + this.width && py > ly && py < ly + this.height); 24 | 25 | return inside ? new FastVector((pos.x - lx) / this.canvas.width, (pos.y - ly) / this.canvas.height) : null; 26 | }, 27 | 28 | clear: function(){ 29 | this.ctx.clearRect(0, 0, this.width, this.height); 30 | }, 31 | 32 | circle: function(p, r){ 33 | x = p.x * this.width; 34 | y = p.y * this.height; 35 | this.ctx.beginPath(); 36 | this.ctx.moveTo(x + r, y); 37 | this.ctx.arc(x, y, r, 0, two_pi, false); 38 | this.ctx.fill(); 39 | }, 40 | 41 | line: function(x1, x2){ 42 | this.ctx.beginPath(); 43 | this.ctx.moveTo(x1.x * this.width, x1.y * this.height); 44 | this.ctx.lineTo(x2.x * this.width, x2.y * this.height); 45 | this.ctx.stroke(); 46 | } 47 | }; 48 | 49 | })(); 50 | -------------------------------------------------------------------------------- /source/cloth.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEvent('domready', function(){ 3 | var canvas = new Canvas(document.getElement('canvas')), 4 | cloth = new Cloth(canvas), 5 | inputs = {}, point, 6 | key_down, mouse_down, mouse; 7 | 8 | var position = function(event){ 9 | return canvas.adjust({ 10 | x: event.page.x, 11 | y: event.page.y 12 | }); 13 | }; 14 | 15 | var setPoint = function(inv_mass){ 16 | if (!point) return; 17 | if (mouse) { 18 | point.setCurrent(mouse); 19 | point.setPrevious(mouse); 20 | } 21 | point.inv_mass = inv_mass; 22 | }; 23 | 24 | document.addEvents({ 25 | 'keydown': function(event){ 26 | key_down = true; 27 | }, 28 | 29 | 'keyup': function(){ 30 | key_down = false; 31 | }, 32 | 33 | 'mousedown': function(event){ 34 | mouse_down = true; 35 | mouse = position(event); 36 | 37 | if (!mouse) return; 38 | 39 | point = cloth.getClosestPoint(mouse); 40 | setPoint(0); 41 | }, 42 | 43 | 'mouseup': function(event){ 44 | mouse_down = false; 45 | if (mouse) setPoint( key_down ? 0 : 1); 46 | }, 47 | 48 | 'mousemove': function(event){ 49 | if (!mouse_down) return; 50 | 51 | mouse = position(event); 52 | setPoint(mouse ? 0 : 1); 53 | } 54 | }); 55 | 56 | document.getElements('input').each(function(input){ 57 | inputs[input.getProperty('id')] = input; 58 | }); 59 | 60 | inputs.points.addEvent('click', cloth.togglePoints.bind(cloth)); 61 | inputs.constraints.addEvent('click', cloth.toggleConstraints.bind(cloth)); 62 | 63 | cloth.draw_points = inputs.points.checked; 64 | cloth.draw_constraints = inputs.constraints.checked; 65 | 66 | setInterval(cloth.update.bind(cloth), 35); 67 | }); 68 | 69 | var Cloth = function(canvas){ 70 | 71 | var max_points = 15, 72 | width = canvas.width, 73 | height = canvas.height, 74 | max_dim = Math.max(width, height), 75 | min_dim = Math.min(width, height), 76 | x_offset = width * 0.2, 77 | y_offset = height * 0.2, 78 | spacing = (max_dim - (Math.max(x_offset, y_offset) * 2)) / max_points; 79 | 80 | this.num_iterations = 2; 81 | this.canvas = canvas; 82 | this.points = []; 83 | this.constraints = []; 84 | 85 | var num_x_points = this.num_x_points = (max_points * (width / max_dim)).round(); 86 | var num_y_points = this.num_y_points = (max_points * (height / max_dim)).round(); 87 | 88 | var constraint; 89 | 90 | for (var i = 0, y = y_offset; i < num_y_points; i++, y += spacing){ 91 | this.points[i] = []; 92 | 93 | for (var j = 0, x = x_offset; j < num_x_points; j++, x += spacing){ 94 | var point = new Point(canvas, x / width, y / height); 95 | this.points[i][j] = point; 96 | 97 | //add a vertical constraint 98 | if (i > 0){ 99 | constraint = new Constraint(canvas, this.points[i - 1][j], this.points[i][j]); 100 | this.constraints.push(constraint); 101 | } 102 | 103 | //add a new horizontal constraints 104 | if (j > 0){ 105 | constraint = new Constraint(canvas, this.points[i][j - 1], this.points[i][j]); 106 | this.constraints.push(constraint); 107 | } 108 | } 109 | } 110 | //pin the top right and top left. 111 | this.points[0][0].inv_mass = 0; 112 | this.points[0][(num_x_points / 2).floor()].inv_mass = 0; 113 | this.points[0][num_x_points - 1].inv_mass = 0; 114 | 115 | this.num_constraints = this.constraints.length; 116 | 117 | for (i = 0; i < this.num_constraints; i++) 118 | this.constraints[i].draw(); 119 | 120 | }; 121 | 122 | Cloth.prototype = { 123 | 124 | update: function() { 125 | this.canvas.clear(); 126 | 127 | var num_x = this.num_x_points, 128 | num_y = this.num_y_points, 129 | num_c = this.num_constraints, 130 | num_i = this.num_iterations, 131 | i, j; 132 | 133 | //move each point with a pull from gravity 134 | for (i = 0; i < num_y; i++) 135 | for (j = 0; j < num_x; j++) 136 | this.points[i][j].move(); 137 | 138 | //make sure all the constraints are satisfied. 139 | for (j = 0; j < num_i; j++) 140 | for (i = 0; i < num_c; i++) 141 | this.constraints[i].satisfy(); 142 | 143 | //draw the necessary components. 144 | if (this.draw_constraints) 145 | for (i = 0; i < this.num_constraints; i++) 146 | this.constraints[i].draw(); 147 | 148 | if (this.draw_points) 149 | for (i = 0; i < this.num_y_points; i++) 150 | for (j = 0; j < this.num_x_points; j++) 151 | this.points[i][j].draw(); 152 | 153 | }, 154 | 155 | getClosestPoint: function(pos) { 156 | var min_dist = 1, 157 | min_point = null, 158 | num_x = this.num_x_points, 159 | num_y = this.num_y_points, 160 | dist, i, j; 161 | 162 | for (i = 0; i < num_y; i++){ 163 | for (j = 0; j < num_x; j++){ 164 | dist = pos.subtract(this.points[i][j].getCurrent()).length(); 165 | 166 | if (dist < min_dist){ 167 | min_dist = dist; 168 | min_point = this.points[i][j]; 169 | } 170 | } 171 | } 172 | 173 | return min_point; 174 | }, 175 | 176 | toggleConstraints: function(){ 177 | this.draw_constraints = !this.draw_constraints; 178 | }, 179 | 180 | togglePoints: function(){ 181 | this.draw_points = !this.draw_points; 182 | } 183 | }; 184 | -------------------------------------------------------------------------------- /source/constraint.js: -------------------------------------------------------------------------------- 1 | 2 | var Constraint = function(canvas, p1, p2, rl){ 3 | this.canvas = canvas; 4 | this.p1 = p1; 5 | this.p2 = p2; 6 | this.rest_length = rl || p1.getCurrent().subtract(p2.getCurrent()).length(); 7 | this.squared_rest_length = this.rest_length * this.rest_length; 8 | }; 9 | 10 | Constraint.prototype = { 11 | draw: function(){ 12 | this.canvas.line(this.p1.getCurrent(), this.p2.getCurrent()); 13 | }, 14 | 15 | satisfy: function(){ 16 | var p1 = this.p1.getCurrent(); 17 | var p2 = this.p2.getCurrent(); 18 | var delta = p2.subtract(p1); 19 | 20 | var p1_im = this.p1.inv_mass; 21 | var p2_im = this.p2.inv_mass; 22 | 23 | var d = delta.squaredLength(); 24 | 25 | var diff = (d - this.squared_rest_length) / ((this.squared_rest_length + d) * (p1_im + p2_im)); 26 | 27 | if (p1_im != 0){ 28 | this.p1.setCurrent(p1.add(delta.multiply(p1_im * diff))); 29 | } 30 | 31 | if (p2_im != 0){ 32 | this.p2.setCurrent( p2.subtract(delta.multiply(p2_im*diff)) ); 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /source/fast_vector.js: -------------------------------------------------------------------------------- 1 | 2 | var FastVector = function(x,y){ 3 | this.x = x; 4 | this.y = y; 5 | }; 6 | 7 | FastVector.prototype = { 8 | 9 | add: function (B,internal) { 10 | var nx, ny; 11 | if (typeof(B)=='number'){ 12 | nx = this.x+B; 13 | ny = this.y+B; 14 | }else{ 15 | nx = this.x+B.x; 16 | ny = this.y+B.y; 17 | } 18 | return new FastVector(nx,ny); 19 | }, 20 | add_: function(B) { 21 | if (typeof(B)=='number'){ 22 | this.x+=B; this.y+=B; 23 | }else{ 24 | this.x+=B.x; this.y+=B.y; 25 | } 26 | return this; 27 | }, 28 | dot: function(B) { 29 | return ((this.x*B.x)+(this.y*B.y)); 30 | }, 31 | length: function() { 32 | return Math.sqrt((this.x*this.x)+(this.y*this.y)); 33 | }, 34 | multiply: function(B) { 35 | var nx, ny; 36 | if (typeof(B)=='number'){ 37 | nx = this.x*B; ny = this.y*B; 38 | }else{ 39 | nx = this.x*B.x; ny = this.y*B.y; 40 | } 41 | return new FastVector(nx,ny); 42 | }, 43 | multiply_: function(B) { 44 | if (typeof(B)=='number'){ 45 | this.x*=B; this.y*=B; 46 | }else{ 47 | this.x*=B.x; this.y*=B.y; 48 | } 49 | return this; 50 | }, 51 | squaredLength: function(args) { 52 | return (this.x*this.x)+(this.y*this.y); 53 | }, 54 | sum: function(){ 55 | return this.x+this.y; 56 | }, 57 | subtract: function(B) { 58 | var nx, ny; 59 | if (typeof(B) == 'number'){ 60 | nx = this.x-B; ny = this.y-B; 61 | }else{ 62 | nx = this.x-B.x; ny = this.y-B.y; 63 | } 64 | return new FastVector(nx,ny); 65 | }, 66 | subtract_: function(B) { 67 | if (typeof(B) == 'number'){ 68 | this.x-=B; this.y-=B; 69 | }else{ 70 | this.x-=B.x; this.y-=B.y; 71 | } 72 | return this; 73 | }, 74 | toString: function() { 75 | return "["+this.x+","+this.y+"]"; 76 | } 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /source/point.js: -------------------------------------------------------------------------------- 1 | 2 | var Point = function(canvas, x, y){ 3 | this.canvas = canvas; 4 | this.current = this.previous = new FastVector(x, y); 5 | 6 | this.mass = this.inv_mass = 1; 7 | 8 | this.force = new FastVector(0.0,0.5).multiply(0.05 * 0.05); 9 | this.radius = 3; 10 | }; 11 | 12 | Point.prototype = { 13 | 14 | setCurrent: function(p) { 15 | this.current = p; 16 | }, 17 | 18 | setPrevious: function(p) { 19 | this.previous = p; 20 | }, 21 | 22 | getCurrent: function() { 23 | return this.current; 24 | }, 25 | 26 | getPrevious: function() { 27 | return this.previous; 28 | }, 29 | 30 | move: function() { 31 | if (this.inv_mass!=0){ 32 | var new_pos = this.current.multiply(1.99).subtract(this.previous.multiply(0.99)).add(this.force); 33 | new_pos.x = (new_pos.x < 0) ? 0 : ((new_pos.x > 1) ? 1 : new_pos.x); 34 | new_pos.y = (new_pos.y < 0) ? 0 : ((new_pos.y > 1) ? 1 : new_pos.y); 35 | this.previous = this.current; 36 | this.current = new_pos; 37 | } 38 | }, 39 | 40 | draw: function() { 41 | this.canvas.circle(this.current, this.radius); 42 | } 43 | 44 | }; 45 | --------------------------------------------------------------------------------