js/src/devtools/jint/v8/raytrace.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 // The ray tracer code in this file is written by Adam Burmister. It
     2 // is available in its original form from:
     3 //
     4 //   http://labs.flog.nz.co/raytracer/
     5 //
     6 // It has been modified slightly by Google to work as a standalone
     7 // benchmark, but the all the computational code remains
     8 // untouched. This file also contains a copy of the Prototype
     9 // JavaScript framework which is used by the ray tracer.
    11 var RayTrace = new BenchmarkSuite('RayTrace', 932666, [
    12   new Benchmark('RayTrace', renderScene)
    13 ]);
    16 // Create dummy objects if we're not running in a browser.
    17 if (typeof document == 'undefined') {
    18   document = { };
    19   window = { opera: null };
    20   navigator = { userAgent: null, appVersion: "" };
    21 }
    24 // ------------------------------------------------------------------------
    25 // ------------------------------------------------------------------------
    28 /*  Prototype JavaScript framework, version 1.5.0
    29  *  (c) 2005-2007 Sam Stephenson
    30  *
    31  *  Prototype is freely distributable under the terms of an MIT-style license.
    32  *  For details, see the Prototype web site: http://prototype.conio.net/
    33  *
    34 /*--------------------------------------------------------------------------*/
    36 //--------------------
    37 var Prototype = {
    38   Version: '1.5.0',
    39   BrowserFeatures: {
    40     XPath: !!document.evaluate
    41   },
    43   ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
    44   emptyFunction: function() {},
    45   K: function(x) { return x }
    46 }
    48 var Class = {
    49   create: function() {
    50     return function() {
    51       this.initialize.apply(this, arguments);
    52     }
    53   }
    54 }
    56 var Abstract = new Object();
    58 Object.extend = function(destination, source) {
    59   for (var property in source) {
    60     destination[property] = source[property];
    61   }
    62   return destination;
    63 }
    65 Object.extend(Object, {
    66   inspect: function(object) {
    67     try {
    68       if (object === undefined) return 'undefined';
    69       if (object === null) return 'null';
    70       return object.inspect ? object.inspect() : object.toString();
    71     } catch (e) {
    72       if (e instanceof RangeError) return '...';
    73       throw e;
    74     }
    75   },
    77   keys: function(object) {
    78     var keys = [];
    79     for (var property in object)
    80       keys.push(property);
    81     return keys;
    82   },
    84   values: function(object) {
    85     var values = [];
    86     for (var property in object)
    87       values.push(object[property]);
    88     return values;
    89   },
    91   clone: function(object) {
    92     return Object.extend({}, object);
    93   }
    94 });
    96 Function.prototype.bind = function() {
    97   var __method = this, args = $A(arguments), object = args.shift();
    98   return function() {
    99     return __method.apply(object, args.concat($A(arguments)));
   100   }
   101 }
   103 Function.prototype.bindAsEventListener = function(object) {
   104   var __method = this, args = $A(arguments), object = args.shift();
   105   return function(event) {
   106     return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
   107   }
   108 }
   110 Object.extend(Number.prototype, {
   111   toColorPart: function() {
   112     var digits = this.toString(16);
   113     if (this < 16) return '0' + digits;
   114     return digits;
   115   },
   117   succ: function() {
   118     return this + 1;
   119   },
   121   times: function(iterator) {
   122     $R(0, this, true).each(iterator);
   123     return this;
   124   }
   125 });
   127 var Try = {
   128   these: function() {
   129     var returnValue;
   131     for (var i = 0, length = arguments.length; i < length; i++) {
   132       var lambda = arguments[i];
   133       try {
   134         returnValue = lambda();
   135         break;
   136       } catch (e) {}
   137     }
   139     return returnValue;
   140   }
   141 }
   143 /*--------------------------------------------------------------------------*/
   145 var PeriodicalExecuter = Class.create();
   146 PeriodicalExecuter.prototype = {
   147   initialize: function(callback, frequency) {
   148     this.callback = callback;
   149     this.frequency = frequency;
   150     this.currentlyExecuting = false;
   152     this.registerCallback();
   153   },
   155   registerCallback: function() {
   156     this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
   157   },
   159   stop: function() {
   160     if (!this.timer) return;
   161     clearInterval(this.timer);
   162     this.timer = null;
   163   },
   165   onTimerEvent: function() {
   166     if (!this.currentlyExecuting) {
   167       try {
   168         this.currentlyExecuting = true;
   169         this.callback(this);
   170       } finally {
   171         this.currentlyExecuting = false;
   172       }
   173     }
   174   }
   175 }
   176 String.interpret = function(value){
   177   return value == null ? '' : String(value);
   178 }
   180 Object.extend(String.prototype, {
   181   gsub: function(pattern, replacement) {
   182     var result = '', source = this, match;
   183     replacement = arguments.callee.prepareReplacement(replacement);
   185     while (source.length > 0) {
   186       if (match = source.match(pattern)) {
   187         result += source.slice(0, match.index);
   188         result += String.interpret(replacement(match));
   189         source  = source.slice(match.index + match[0].length);
   190       } else {
   191         result += source, source = '';
   192       }
   193     }
   194     return result;
   195   },
   197   sub: function(pattern, replacement, count) {
   198     replacement = this.gsub.prepareReplacement(replacement);
   199     count = count === undefined ? 1 : count;
   201     return this.gsub(pattern, function(match) {
   202       if (--count < 0) return match[0];
   203       return replacement(match);
   204     });
   205   },
   207   scan: function(pattern, iterator) {
   208     this.gsub(pattern, iterator);
   209     return this;
   210   },
   212   truncate: function(length, truncation) {
   213     length = length || 30;
   214     truncation = truncation === undefined ? '...' : truncation;
   215     return this.length > length ?
   216       this.slice(0, length - truncation.length) + truncation : this;
   217   },
   219   strip: function() {
   220     return this.replace(/^\s+/, '').replace(/\s+$/, '');
   221   },
   223   stripTags: function() {
   224     return this.replace(/<\/?[^>]+>/gi, '');
   225   },
   227   stripScripts: function() {
   228     return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
   229   },
   231   extractScripts: function() {
   232     var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
   233     var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
   234     return (this.match(matchAll) || []).map(function(scriptTag) {
   235       return (scriptTag.match(matchOne) || ['', ''])[1];
   236     });
   237   },
   239   evalScripts: function() {
   240     return this.extractScripts().map(function(script) { return eval(script) });
   241   },
   243   escapeHTML: function() {
   244     var div = document.createElement('div');
   245     var text = document.createTextNode(this);
   246     div.appendChild(text);
   247     return div.innerHTML;
   248   },
   250   unescapeHTML: function() {
   251     var div = document.createElement('div');
   252     div.innerHTML = this.stripTags();
   253     return div.childNodes[0] ? (div.childNodes.length > 1 ?
   254       $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
   255       div.childNodes[0].nodeValue) : '';
   256   },
   258   toQueryParams: function(separator) {
   259     var match = this.strip().match(/([^?#]*)(#.*)?$/);
   260     if (!match) return {};
   262     return match[1].split(separator || '&').inject({}, function(hash, pair) {
   263       if ((pair = pair.split('='))[0]) {
   264         var name = decodeURIComponent(pair[0]);
   265         var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
   267         if (hash[name] !== undefined) {
   268           if (hash[name].constructor != Array)
   269             hash[name] = [hash[name]];
   270           if (value) hash[name].push(value);
   271         }
   272         else hash[name] = value;
   273       }
   274       return hash;
   275     });
   276   },
   278   toArray: function() {
   279     return this.split('');
   280   },
   282   succ: function() {
   283     return this.slice(0, this.length - 1) +
   284       String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
   285   },
   287   camelize: function() {
   288     var parts = this.split('-'), len = parts.length;
   289     if (len == 1) return parts[0];
   291     var camelized = this.charAt(0) == '-'
   292       ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
   293       : parts[0];
   295     for (var i = 1; i < len; i++)
   296       camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
   298     return camelized;
   299   },
   301   capitalize: function(){
   302     return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
   303   },
   305   underscore: function() {
   306     return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
   307   },
   309   dasherize: function() {
   310     return this.gsub(/_/,'-');
   311   },
   313   inspect: function(useDoubleQuotes) {
   314     var escapedString = this.replace(/\\/g, '\\\\');
   315     if (useDoubleQuotes)
   316       return '"' + escapedString.replace(/"/g, '\\"') + '"';
   317     else
   318       return "'" + escapedString.replace(/'/g, '\\\'') + "'";
   319   }
   320 });
   322 String.prototype.gsub.prepareReplacement = function(replacement) {
   323   if (typeof replacement == 'function') return replacement;
   324   var template = new Template(replacement);
   325   return function(match) { return template.evaluate(match) };
   326 }
   328 String.prototype.parseQuery = String.prototype.toQueryParams;
   330 var Template = Class.create();
   331 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
   332 Template.prototype = {
   333   initialize: function(template, pattern) {
   334     this.template = template.toString();
   335     this.pattern  = pattern || Template.Pattern;
   336   },
   338   evaluate: function(object) {
   339     return this.template.gsub(this.pattern, function(match) {
   340       var before = match[1];
   341       if (before == '\\') return match[2];
   342       return before + String.interpret(object[match[3]]);
   343     });
   344   }
   345 }
   347 var $break    = new Object();
   348 var $continue = new Object();
   350 var Enumerable = {
   351   each: function(iterator) {
   352     var index = 0;
   353     try {
   354       this._each(function(value) {
   355         try {
   356           iterator(value, index++);
   357         } catch (e) {
   358           if (e != $continue) throw e;
   359         }
   360       });
   361     } catch (e) {
   362       if (e != $break) throw e;
   363     }
   364     return this;
   365   },
   367   eachSlice: function(number, iterator) {
   368     var index = -number, slices = [], array = this.toArray();
   369     while ((index += number) < array.length)
   370       slices.push(array.slice(index, index+number));
   371     return slices.map(iterator);
   372   },
   374   all: function(iterator) {
   375     var result = true;
   376     this.each(function(value, index) {
   377       result = result && !!(iterator || Prototype.K)(value, index);
   378       if (!result) throw $break;
   379     });
   380     return result;
   381   },
   383   any: function(iterator) {
   384     var result = false;
   385     this.each(function(value, index) {
   386       if (result = !!(iterator || Prototype.K)(value, index))
   387         throw $break;
   388     });
   389     return result;
   390   },
   392   collect: function(iterator) {
   393     var results = [];
   394     this.each(function(value, index) {
   395       results.push((iterator || Prototype.K)(value, index));
   396     });
   397     return results;
   398   },
   400   detect: function(iterator) {
   401     var result;
   402     this.each(function(value, index) {
   403       if (iterator(value, index)) {
   404         result = value;
   405         throw $break;
   406       }
   407     });
   408     return result;
   409   },
   411   findAll: function(iterator) {
   412     var results = [];
   413     this.each(function(value, index) {
   414       if (iterator(value, index))
   415         results.push(value);
   416     });
   417     return results;
   418   },
   420   grep: function(pattern, iterator) {
   421     var results = [];
   422     this.each(function(value, index) {
   423       var stringValue = value.toString();
   424       if (stringValue.match(pattern))
   425         results.push((iterator || Prototype.K)(value, index));
   426     })
   427     return results;
   428   },
   430   include: function(object) {
   431     var found = false;
   432     this.each(function(value) {
   433       if (value == object) {
   434         found = true;
   435         throw $break;
   436       }
   437     });
   438     return found;
   439   },
   441   inGroupsOf: function(number, fillWith) {
   442     fillWith = fillWith === undefined ? null : fillWith;
   443     return this.eachSlice(number, function(slice) {
   444       while(slice.length < number) slice.push(fillWith);
   445       return slice;
   446     });
   447   },
   449   inject: function(memo, iterator) {
   450     this.each(function(value, index) {
   451       memo = iterator(memo, value, index);
   452     });
   453     return memo;
   454   },
   456   invoke: function(method) {
   457     var args = $A(arguments).slice(1);
   458     return this.map(function(value) {
   459       return value[method].apply(value, args);
   460     });
   461   },
   463   max: function(iterator) {
   464     var result;
   465     this.each(function(value, index) {
   466       value = (iterator || Prototype.K)(value, index);
   467       if (result == undefined || value >= result)
   468         result = value;
   469     });
   470     return result;
   471   },
   473   min: function(iterator) {
   474     var result;
   475     this.each(function(value, index) {
   476       value = (iterator || Prototype.K)(value, index);
   477       if (result == undefined || value < result)
   478         result = value;
   479     });
   480     return result;
   481   },
   483   partition: function(iterator) {
   484     var trues = [], falses = [];
   485     this.each(function(value, index) {
   486       ((iterator || Prototype.K)(value, index) ?
   487         trues : falses).push(value);
   488     });
   489     return [trues, falses];
   490   },
   492   pluck: function(property) {
   493     var results = [];
   494     this.each(function(value, index) {
   495       results.push(value[property]);
   496     });
   497     return results;
   498   },
   500   reject: function(iterator) {
   501     var results = [];
   502     this.each(function(value, index) {
   503       if (!iterator(value, index))
   504         results.push(value);
   505     });
   506     return results;
   507   },
   509   sortBy: function(iterator) {
   510     return this.map(function(value, index) {
   511       return {value: value, criteria: iterator(value, index)};
   512     }).sort(function(left, right) {
   513       var a = left.criteria, b = right.criteria;
   514       return a < b ? -1 : a > b ? 1 : 0;
   515     }).pluck('value');
   516   },
   518   toArray: function() {
   519     return this.map();
   520   },
   522   zip: function() {
   523     var iterator = Prototype.K, args = $A(arguments);
   524     if (typeof args.last() == 'function')
   525       iterator = args.pop();
   527     var collections = [this].concat(args).map($A);
   528     return this.map(function(value, index) {
   529       return iterator(collections.pluck(index));
   530     });
   531   },
   533   size: function() {
   534     return this.toArray().length;
   535   },
   537   inspect: function() {
   538     return '#<Enumerable:' + this.toArray().inspect() + '>';
   539   }
   540 }
   542 Object.extend(Enumerable, {
   543   map:     Enumerable.collect,
   544   find:    Enumerable.detect,
   545   select:  Enumerable.findAll,
   546   member:  Enumerable.include,
   547   entries: Enumerable.toArray
   548 });
   549 var $A = Array.from = function(iterable) {
   550   if (!iterable) return [];
   551   if (iterable.toArray) {
   552     return iterable.toArray();
   553   } else {
   554     var results = [];
   555     for (var i = 0, length = iterable.length; i < length; i++)
   556       results.push(iterable[i]);
   557     return results;
   558   }
   559 }
   561 Object.extend(Array.prototype, Enumerable);
   563 if (!Array.prototype._reverse)
   564   Array.prototype._reverse = Array.prototype.reverse;
   566 Object.extend(Array.prototype, {
   567   _each: function(iterator) {
   568     for (var i = 0, length = this.length; i < length; i++)
   569       iterator(this[i]);
   570   },
   572   clear: function() {
   573     this.length = 0;
   574     return this;
   575   },
   577   first: function() {
   578     return this[0];
   579   },
   581   last: function() {
   582     return this[this.length - 1];
   583   },
   585   compact: function() {
   586     return this.select(function(value) {
   587       return value != null;
   588     });
   589   },
   591   flatten: function() {
   592     return this.inject([], function(array, value) {
   593       return array.concat(value && value.constructor == Array ?
   594         value.flatten() : [value]);
   595     });
   596   },
   598   without: function() {
   599     var values = $A(arguments);
   600     return this.select(function(value) {
   601       return !values.include(value);
   602     });
   603   },
   605   indexOf: function(object) {
   606     for (var i = 0, length = this.length; i < length; i++)
   607       if (this[i] == object) return i;
   608     return -1;
   609   },
   611   reverse: function(inline) {
   612     return (inline !== false ? this : this.toArray())._reverse();
   613   },
   615   reduce: function() {
   616     return this.length > 1 ? this : this[0];
   617   },
   619   uniq: function() {
   620     return this.inject([], function(array, value) {
   621       return array.include(value) ? array : array.concat([value]);
   622     });
   623   },
   625   clone: function() {
   626     return [].concat(this);
   627   },
   629   size: function() {
   630     return this.length;
   631   },
   633   inspect: function() {
   634     return '[' + this.map(Object.inspect).join(', ') + ']';
   635   }
   636 });
   638 Array.prototype.toArray = Array.prototype.clone;
   640 function $w(string){
   641   string = string.strip();
   642   return string ? string.split(/\s+/) : [];
   643 }
   645 if(window.opera){
   646   Array.prototype.concat = function(){
   647     var array = [];
   648     for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
   649     for(var i = 0, length = arguments.length; i < length; i++) {
   650       if(arguments[i].constructor == Array) {
   651         for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
   652           array.push(arguments[i][j]);
   653       } else {
   654         array.push(arguments[i]);
   655       }
   656     }
   657     return array;
   658   }
   659 }
   660 var Hash = function(obj) {
   661   Object.extend(this, obj || {});
   662 };
   664 Object.extend(Hash, {
   665   toQueryString: function(obj) {
   666     var parts = [];
   668 	  this.prototype._each.call(obj, function(pair) {
   669       if (!pair.key) return;
   671       if (pair.value && pair.value.constructor == Array) {
   672         var values = pair.value.compact();
   673         if (values.length < 2) pair.value = values.reduce();
   674         else {
   675         	key = encodeURIComponent(pair.key);
   676           values.each(function(value) {
   677             value = value != undefined ? encodeURIComponent(value) : '';
   678             parts.push(key + '=' + encodeURIComponent(value));
   679           });
   680           return;
   681         }
   682       }
   683       if (pair.value == undefined) pair[1] = '';
   684       parts.push(pair.map(encodeURIComponent).join('='));
   685 	  });
   687     return parts.join('&');
   688   }
   689 });
   691 Object.extend(Hash.prototype, Enumerable);
   692 Object.extend(Hash.prototype, {
   693   _each: function(iterator) {
   694     for (var key in this) {
   695       var value = this[key];
   696       if (value && value == Hash.prototype[key]) continue;
   698       var pair = [key, value];
   699       pair.key = key;
   700       pair.value = value;
   701       iterator(pair);
   702     }
   703   },
   705   keys: function() {
   706     return this.pluck('key');
   707   },
   709   values: function() {
   710     return this.pluck('value');
   711   },
   713   merge: function(hash) {
   714     return $H(hash).inject(this, function(mergedHash, pair) {
   715       mergedHash[pair.key] = pair.value;
   716       return mergedHash;
   717     });
   718   },
   720   remove: function() {
   721     var result;
   722     for(var i = 0, length = arguments.length; i < length; i++) {
   723       var value = this[arguments[i]];
   724       if (value !== undefined){
   725         if (result === undefined) result = value;
   726         else {
   727           if (result.constructor != Array) result = [result];
   728           result.push(value)
   729         }
   730       }
   731       delete this[arguments[i]];
   732     }
   733     return result;
   734   },
   736   toQueryString: function() {
   737     return Hash.toQueryString(this);
   738   },
   740   inspect: function() {
   741     return '#<Hash:{' + this.map(function(pair) {
   742       return pair.map(Object.inspect).join(': ');
   743     }).join(', ') + '}>';
   744   }
   745 });
   747 function $H(object) {
   748   if (object && object.constructor == Hash) return object;
   749   return new Hash(object);
   750 };
   751 ObjectRange = Class.create();
   752 Object.extend(ObjectRange.prototype, Enumerable);
   753 Object.extend(ObjectRange.prototype, {
   754   initialize: function(start, end, exclusive) {
   755     this.start = start;
   756     this.end = end;
   757     this.exclusive = exclusive;
   758   },
   760   _each: function(iterator) {
   761     var value = this.start;
   762     while (this.include(value)) {
   763       iterator(value);
   764       value = value.succ();
   765     }
   766   },
   768   include: function(value) {
   769     if (value < this.start)
   770       return false;
   771     if (this.exclusive)
   772       return value < this.end;
   773     return value <= this.end;
   774   }
   775 });
   777 var $R = function(start, end, exclusive) {
   778   return new ObjectRange(start, end, exclusive);
   779 }
   781 var Ajax = {
   782   getTransport: function() {
   783     return Try.these(
   784       function() {return new XMLHttpRequest()},
   785       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
   786       function() {return new ActiveXObject('Microsoft.XMLHTTP')}
   787     ) || false;
   788   },
   790   activeRequestCount: 0
   791 }
   793 Ajax.Responders = {
   794   responders: [],
   796   _each: function(iterator) {
   797     this.responders._each(iterator);
   798   },
   800   register: function(responder) {
   801     if (!this.include(responder))
   802       this.responders.push(responder);
   803   },
   805   unregister: function(responder) {
   806     this.responders = this.responders.without(responder);
   807   },
   809   dispatch: function(callback, request, transport, json) {
   810     this.each(function(responder) {
   811       if (typeof responder[callback] == 'function') {
   812         try {
   813           responder[callback].apply(responder, [request, transport, json]);
   814         } catch (e) {}
   815       }
   816     });
   817   }
   818 };
   820 Object.extend(Ajax.Responders, Enumerable);
   822 Ajax.Responders.register({
   823   onCreate: function() {
   824     Ajax.activeRequestCount++;
   825   },
   826   onComplete: function() {
   827     Ajax.activeRequestCount--;
   828   }
   829 });
   831 Ajax.Base = function() {};
   832 Ajax.Base.prototype = {
   833   setOptions: function(options) {
   834     this.options = {
   835       method:       'post',
   836       asynchronous: true,
   837       contentType:  'application/x-www-form-urlencoded',
   838       encoding:     'UTF-8',
   839       parameters:   ''
   840     }
   841     Object.extend(this.options, options || {});
   843     this.options.method = this.options.method.toLowerCase();
   844     if (typeof this.options.parameters == 'string')
   845       this.options.parameters = this.options.parameters.toQueryParams();
   846   }
   847 }
   849 Ajax.Request = Class.create();
   850 Ajax.Request.Events =
   851   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
   853 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
   854   _complete: false,
   856   initialize: function(url, options) {
   857     this.transport = Ajax.getTransport();
   858     this.setOptions(options);
   859     this.request(url);
   860   },
   862   request: function(url) {
   863     this.url = url;
   864     this.method = this.options.method;
   865     var params = this.options.parameters;
   867     if (!['get', 'post'].include(this.method)) {
   868       // simulate other verbs over post
   869       params['_method'] = this.method;
   870       this.method = 'post';
   871     }
   873     params = Hash.toQueryString(params);
   874     if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
   876     // when GET, append parameters to URL
   877     if (this.method == 'get' && params)
   878       this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
   880     try {
   881       Ajax.Responders.dispatch('onCreate', this, this.transport);
   883       this.transport.open(this.method.toUpperCase(), this.url,
   884         this.options.asynchronous);
   886       if (this.options.asynchronous)
   887         setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
   889       this.transport.onreadystatechange = this.onStateChange.bind(this);
   890       this.setRequestHeaders();
   892       var body = this.method == 'post' ? (this.options.postBody || params) : null;
   894       this.transport.send(body);
   896       /* Force Firefox to handle ready state 4 for synchronous requests */
   897       if (!this.options.asynchronous && this.transport.overrideMimeType)
   898         this.onStateChange();
   900     }
   901     catch (e) {
   902       this.dispatchException(e);
   903     }
   904   },
   906   onStateChange: function() {
   907     var readyState = this.transport.readyState;
   908     if (readyState > 1 && !((readyState == 4) && this._complete))
   909       this.respondToReadyState(this.transport.readyState);
   910   },
   912   setRequestHeaders: function() {
   913     var headers = {
   914       'X-Requested-With': 'XMLHttpRequest',
   915       'X-Prototype-Version': Prototype.Version,
   916       'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
   917     };
   919     if (this.method == 'post') {
   920       headers['Content-type'] = this.options.contentType +
   921         (this.options.encoding ? '; charset=' + this.options.encoding : '');
   923       /* Force "Connection: close" for older Mozilla browsers to work
   924        * around a bug where XMLHttpRequest sends an incorrect
   925        * Content-length header. See Mozilla Bugzilla #246651.
   926        */
   927       if (this.transport.overrideMimeType &&
   928           (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
   929             headers['Connection'] = 'close';
   930     }
   932     // user-defined headers
   933     if (typeof this.options.requestHeaders == 'object') {
   934       var extras = this.options.requestHeaders;
   936       if (typeof extras.push == 'function')
   937         for (var i = 0, length = extras.length; i < length; i += 2)
   938           headers[extras[i]] = extras[i+1];
   939       else
   940         $H(extras).each(function(pair) { headers[pair.key] = pair.value });
   941     }
   943     for (var name in headers)
   944       this.transport.setRequestHeader(name, headers[name]);
   945   },
   947   success: function() {
   948     return !this.transport.status
   949         || (this.transport.status >= 200 && this.transport.status < 300);
   950   },
   952   respondToReadyState: function(readyState) {
   953     var state = Ajax.Request.Events[readyState];
   954     var transport = this.transport, json = this.evalJSON();
   956     if (state == 'Complete') {
   957       try {
   958         this._complete = true;
   959         (this.options['on' + this.transport.status]
   960          || this.options['on' + (this.success() ? 'Success' : 'Failure')]
   961          || Prototype.emptyFunction)(transport, json);
   962       } catch (e) {
   963         this.dispatchException(e);
   964       }
   966       if ((this.getHeader('Content-type') || 'text/javascript').strip().
   967         match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
   968           this.evalResponse();
   969     }
   971     try {
   972       (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
   973       Ajax.Responders.dispatch('on' + state, this, transport, json);
   974     } catch (e) {
   975       this.dispatchException(e);
   976     }
   978     if (state == 'Complete') {
   979       // avoid memory leak in MSIE: clean up
   980       this.transport.onreadystatechange = Prototype.emptyFunction;
   981     }
   982   },
   984   getHeader: function(name) {
   985     try {
   986       return this.transport.getResponseHeader(name);
   987     } catch (e) { return null }
   988   },
   990   evalJSON: function() {
   991     try {
   992       var json = this.getHeader('X-JSON');
   993       return json ? eval('(' + json + ')') : null;
   994     } catch (e) { return null }
   995   },
   997   evalResponse: function() {
   998     try {
   999       return eval(this.transport.responseText);
  1000     } catch (e) {
  1001       this.dispatchException(e);
  1003   },
  1005   dispatchException: function(exception) {
  1006     (this.options.onException || Prototype.emptyFunction)(this, exception);
  1007     Ajax.Responders.dispatch('onException', this, exception);
  1009 });
  1011 Ajax.Updater = Class.create();
  1013 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  1014   initialize: function(container, url, options) {
  1015     this.container = {
  1016       success: (container.success || container),
  1017       failure: (container.failure || (container.success ? null : container))
  1020     this.transport = Ajax.getTransport();
  1021     this.setOptions(options);
  1023     var onComplete = this.options.onComplete || Prototype.emptyFunction;
  1024     this.options.onComplete = (function(transport, param) {
  1025       this.updateContent();
  1026       onComplete(transport, param);
  1027     }).bind(this);
  1029     this.request(url);
  1030   },
  1032   updateContent: function() {
  1033     var receiver = this.container[this.success() ? 'success' : 'failure'];
  1034     var response = this.transport.responseText;
  1036     if (!this.options.evalScripts) response = response.stripScripts();
  1038     if (receiver = $(receiver)) {
  1039       if (this.options.insertion)
  1040         new this.options.insertion(receiver, response);
  1041       else
  1042         receiver.update(response);
  1045     if (this.success()) {
  1046       if (this.onComplete)
  1047         setTimeout(this.onComplete.bind(this), 10);
  1050 });
  1052 Ajax.PeriodicalUpdater = Class.create();
  1053 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  1054   initialize: function(container, url, options) {
  1055     this.setOptions(options);
  1056     this.onComplete = this.options.onComplete;
  1058     this.frequency = (this.options.frequency || 2);
  1059     this.decay = (this.options.decay || 1);
  1061     this.updater = {};
  1062     this.container = container;
  1063     this.url = url;
  1065     this.start();
  1066   },
  1068   start: function() {
  1069     this.options.onComplete = this.updateComplete.bind(this);
  1070     this.onTimerEvent();
  1071   },
  1073   stop: function() {
  1074     this.updater.options.onComplete = undefined;
  1075     clearTimeout(this.timer);
  1076     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  1077   },
  1079   updateComplete: function(request) {
  1080     if (this.options.decay) {
  1081       this.decay = (request.responseText == this.lastText ?
  1082         this.decay * this.options.decay : 1);
  1084       this.lastText = request.responseText;
  1086     this.timer = setTimeout(this.onTimerEvent.bind(this),
  1087       this.decay * this.frequency * 1000);
  1088   },
  1090   onTimerEvent: function() {
  1091     this.updater = new Ajax.Updater(this.container, this.url, this.options);
  1093 });
  1094 function $(element) {
  1095   if (arguments.length > 1) {
  1096     for (var i = 0, elements = [], length = arguments.length; i < length; i++)
  1097       elements.push($(arguments[i]));
  1098     return elements;
  1100   if (typeof element == 'string')
  1101     element = document.getElementById(element);
  1102   return Element.extend(element);
  1105 if (Prototype.BrowserFeatures.XPath) {
  1106   document._getElementsByXPath = function(expression, parentElement) {
  1107     var results = [];
  1108     var query = document.evaluate(expression, $(parentElement) || document,
  1109       null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  1110     for (var i = 0, length = query.snapshotLength; i < length; i++)
  1111       results.push(query.snapshotItem(i));
  1112     return results;
  1113   };
  1116 document.getElementsByClassName = function(className, parentElement) {
  1117   if (Prototype.BrowserFeatures.XPath) {
  1118     var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
  1119     return document._getElementsByXPath(q, parentElement);
  1120   } else {
  1121     var children = ($(parentElement) || document.body).getElementsByTagName('*');
  1122     var elements = [], child;
  1123     for (var i = 0, length = children.length; i < length; i++) {
  1124       child = children[i];
  1125       if (Element.hasClassName(child, className))
  1126         elements.push(Element.extend(child));
  1128     return elements;
  1130 };
  1132 /*--------------------------------------------------------------------------*/
  1134 if (!window.Element)
  1135   var Element = new Object();
  1137 Element.extend = function(element) {
  1138   if (!element || _nativeExtensions || element.nodeType == 3) return element;
  1140   if (!element._extended && element.tagName && element != window) {
  1141     var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
  1143     if (element.tagName == 'FORM')
  1144       Object.extend(methods, Form.Methods);
  1145     if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
  1146       Object.extend(methods, Form.Element.Methods);
  1148     Object.extend(methods, Element.Methods.Simulated);
  1150     for (var property in methods) {
  1151       var value = methods[property];
  1152       if (typeof value == 'function' && !(property in element))
  1153         element[property] = cache.findOrStore(value);
  1157   element._extended = true;
  1158   return element;
  1159 };
  1161 Element.extend.cache = {
  1162   findOrStore: function(value) {
  1163     return this[value] = this[value] || function() {
  1164       return value.apply(null, [this].concat($A(arguments)));
  1167 };
  1169 Element.Methods = {
  1170   visible: function(element) {
  1171     return $(element).style.display != 'none';
  1172   },
  1174   toggle: function(element) {
  1175     element = $(element);
  1176     Element[Element.visible(element) ? 'hide' : 'show'](element);
  1177     return element;
  1178   },
  1180   hide: function(element) {
  1181     $(element).style.display = 'none';
  1182     return element;
  1183   },
  1185   show: function(element) {
  1186     $(element).style.display = '';
  1187     return element;
  1188   },
  1190   remove: function(element) {
  1191     element = $(element);
  1192     element.parentNode.removeChild(element);
  1193     return element;
  1194   },
  1196   update: function(element, html) {
  1197     html = typeof html == 'undefined' ? '' : html.toString();
  1198     $(element).innerHTML = html.stripScripts();
  1199     setTimeout(function() {html.evalScripts()}, 10);
  1200     return element;
  1201   },
  1203   replace: function(element, html) {
  1204     element = $(element);
  1205     html = typeof html == 'undefined' ? '' : html.toString();
  1206     if (element.outerHTML) {
  1207       element.outerHTML = html.stripScripts();
  1208     } else {
  1209       var range = element.ownerDocument.createRange();
  1210       range.selectNodeContents(element);
  1211       element.parentNode.replaceChild(
  1212         range.createContextualFragment(html.stripScripts()), element);
  1214     setTimeout(function() {html.evalScripts()}, 10);
  1215     return element;
  1216   },
  1218   inspect: function(element) {
  1219     element = $(element);
  1220     var result = '<' + element.tagName.toLowerCase();
  1221     $H({'id': 'id', 'className': 'class'}).each(function(pair) {
  1222       var property = pair.first(), attribute = pair.last();
  1223       var value = (element[property] || '').toString();
  1224       if (value) result += ' ' + attribute + '=' + value.inspect(true);
  1225     });
  1226     return result + '>';
  1227   },
  1229   recursivelyCollect: function(element, property) {
  1230     element = $(element);
  1231     var elements = [];
  1232     while (element = element[property])
  1233       if (element.nodeType == 1)
  1234         elements.push(Element.extend(element));
  1235     return elements;
  1236   },
  1238   ancestors: function(element) {
  1239     return $(element).recursivelyCollect('parentNode');
  1240   },
  1242   descendants: function(element) {
  1243     return $A($(element).getElementsByTagName('*'));
  1244   },
  1246   immediateDescendants: function(element) {
  1247     if (!(element = $(element).firstChild)) return [];
  1248     while (element && element.nodeType != 1) element = element.nextSibling;
  1249     if (element) return [element].concat($(element).nextSiblings());
  1250     return [];
  1251   },
  1253   previousSiblings: function(element) {
  1254     return $(element).recursivelyCollect('previousSibling');
  1255   },
  1257   nextSiblings: function(element) {
  1258     return $(element).recursivelyCollect('nextSibling');
  1259   },
  1261   siblings: function(element) {
  1262     element = $(element);
  1263     return element.previousSiblings().reverse().concat(element.nextSiblings());
  1264   },
  1266   match: function(element, selector) {
  1267     if (typeof selector == 'string')
  1268       selector = new Selector(selector);
  1269     return selector.match($(element));
  1270   },
  1272   up: function(element, expression, index) {
  1273     return Selector.findElement($(element).ancestors(), expression, index);
  1274   },
  1276   down: function(element, expression, index) {
  1277     return Selector.findElement($(element).descendants(), expression, index);
  1278   },
  1280   previous: function(element, expression, index) {
  1281     return Selector.findElement($(element).previousSiblings(), expression, index);
  1282   },
  1284   next: function(element, expression, index) {
  1285     return Selector.findElement($(element).nextSiblings(), expression, index);
  1286   },
  1288   getElementsBySelector: function() {
  1289     var args = $A(arguments), element = $(args.shift());
  1290     return Selector.findChildElements(element, args);
  1291   },
  1293   getElementsByClassName: function(element, className) {
  1294     return document.getElementsByClassName(className, element);
  1295   },
  1297   readAttribute: function(element, name) {
  1298     element = $(element);
  1299     if (document.all && !window.opera) {
  1300       var t = Element._attributeTranslations;
  1301       if (t.values[name]) return t.values[name](element, name);
  1302       if (t.names[name])  name = t.names[name];
  1303       var attribute = element.attributes[name];
  1304       if(attribute) return attribute.nodeValue;
  1306     return element.getAttribute(name);
  1307   },
  1309   getHeight: function(element) {
  1310     return $(element).getDimensions().height;
  1311   },
  1313   getWidth: function(element) {
  1314     return $(element).getDimensions().width;
  1315   },
  1317   classNames: function(element) {
  1318     return new Element.ClassNames(element);
  1319   },
  1321   hasClassName: function(element, className) {
  1322     if (!(element = $(element))) return;
  1323     var elementClassName = element.className;
  1324     if (elementClassName.length == 0) return false;
  1325     if (elementClassName == className ||
  1326         elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
  1327       return true;
  1328     return false;
  1329   },
  1331   addClassName: function(element, className) {
  1332     if (!(element = $(element))) return;
  1333     Element.classNames(element).add(className);
  1334     return element;
  1335   },
  1337   removeClassName: function(element, className) {
  1338     if (!(element = $(element))) return;
  1339     Element.classNames(element).remove(className);
  1340     return element;
  1341   },
  1343   toggleClassName: function(element, className) {
  1344     if (!(element = $(element))) return;
  1345     Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
  1346     return element;
  1347   },
  1349   observe: function() {
  1350     Event.observe.apply(Event, arguments);
  1351     return $A(arguments).first();
  1352   },
  1354   stopObserving: function() {
  1355     Event.stopObserving.apply(Event, arguments);
  1356     return $A(arguments).first();
  1357   },
  1359   // removes whitespace-only text node children
  1360   cleanWhitespace: function(element) {
  1361     element = $(element);
  1362     var node = element.firstChild;
  1363     while (node) {
  1364       var nextNode = node.nextSibling;
  1365       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
  1366         element.removeChild(node);
  1367       node = nextNode;
  1369     return element;
  1370   },
  1372   empty: function(element) {
  1373     return $(element).innerHTML.match(/^\s*$/);
  1374   },
  1376   descendantOf: function(element, ancestor) {
  1377     element = $(element), ancestor = $(ancestor);
  1378     while (element = element.parentNode)
  1379       if (element == ancestor) return true;
  1380     return false;
  1381   },
  1383   scrollTo: function(element) {
  1384     element = $(element);
  1385     var pos = Position.cumulativeOffset(element);
  1386     window.scrollTo(pos[0], pos[1]);
  1387     return element;
  1388   },
  1390   getStyle: function(element, style) {
  1391     element = $(element);
  1392     if (['float','cssFloat'].include(style))
  1393       style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
  1394     style = style.camelize();
  1395     var value = element.style[style];
  1396     if (!value) {
  1397       if (document.defaultView && document.defaultView.getComputedStyle) {
  1398         var css = document.defaultView.getComputedStyle(element, null);
  1399         value = css ? css[style] : null;
  1400       } else if (element.currentStyle) {
  1401         value = element.currentStyle[style];
  1405     if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
  1406       value = element['offset'+style.capitalize()] + 'px';
  1408     if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
  1409       if (Element.getStyle(element, 'position') == 'static') value = 'auto';
  1410     if(style == 'opacity') {
  1411       if(value) return parseFloat(value);
  1412       if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
  1413         if(value[1]) return parseFloat(value[1]) / 100;
  1414       return 1.0;
  1416     return value == 'auto' ? null : value;
  1417   },
  1419   setStyle: function(element, style) {
  1420     element = $(element);
  1421     for (var name in style) {
  1422       var value = style[name];
  1423       if(name == 'opacity') {
  1424         if (value == 1) {
  1425           value = (/Gecko/.test(navigator.userAgent) &&
  1426             !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
  1427           if(/MSIE/.test(navigator.userAgent) && !window.opera)
  1428             element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
  1429         } else if(value == '') {
  1430           if(/MSIE/.test(navigator.userAgent) && !window.opera)
  1431             element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
  1432         } else {
  1433           if(value < 0.00001) value = 0;
  1434           if(/MSIE/.test(navigator.userAgent) && !window.opera)
  1435             element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
  1436               'alpha(opacity='+value*100+')';
  1438       } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
  1439       element.style[name.camelize()] = value;
  1441     return element;
  1442   },
  1444   getDimensions: function(element) {
  1445     element = $(element);
  1446     var display = $(element).getStyle('display');
  1447     if (display != 'none' && display != null) // Safari bug
  1448       return {width: element.offsetWidth, height: element.offsetHeight};
  1450     // All *Width and *Height properties give 0 on elements with display none,
  1451     // so enable the element temporarily
  1452     var els = element.style;
  1453     var originalVisibility = els.visibility;
  1454     var originalPosition = els.position;
  1455     var originalDisplay = els.display;
  1456     els.visibility = 'hidden';
  1457     els.position = 'absolute';
  1458     els.display = 'block';
  1459     var originalWidth = element.clientWidth;
  1460     var originalHeight = element.clientHeight;
  1461     els.display = originalDisplay;
  1462     els.position = originalPosition;
  1463     els.visibility = originalVisibility;
  1464     return {width: originalWidth, height: originalHeight};
  1465   },
  1467   makePositioned: function(element) {
  1468     element = $(element);
  1469     var pos = Element.getStyle(element, 'position');
  1470     if (pos == 'static' || !pos) {
  1471       element._madePositioned = true;
  1472       element.style.position = 'relative';
  1473       // Opera returns the offset relative to the positioning context, when an
  1474       // element is position relative but top and left have not been defined
  1475       if (window.opera) {
  1476         element.style.top = 0;
  1477         element.style.left = 0;
  1480     return element;
  1481   },
  1483   undoPositioned: function(element) {
  1484     element = $(element);
  1485     if (element._madePositioned) {
  1486       element._madePositioned = undefined;
  1487       element.style.position =
  1488         element.style.top =
  1489         element.style.left =
  1490         element.style.bottom =
  1491         element.style.right = '';
  1493     return element;
  1494   },
  1496   makeClipping: function(element) {
  1497     element = $(element);
  1498     if (element._overflow) return element;
  1499     element._overflow = element.style.overflow || 'auto';
  1500     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
  1501       element.style.overflow = 'hidden';
  1502     return element;
  1503   },
  1505   undoClipping: function(element) {
  1506     element = $(element);
  1507     if (!element._overflow) return element;
  1508     element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
  1509     element._overflow = null;
  1510     return element;
  1512 };
  1514 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
  1516 Element._attributeTranslations = {};
  1518 Element._attributeTranslations.names = {
  1519   colspan:   "colSpan",
  1520   rowspan:   "rowSpan",
  1521   valign:    "vAlign",
  1522   datetime:  "dateTime",
  1523   accesskey: "accessKey",
  1524   tabindex:  "tabIndex",
  1525   enctype:   "encType",
  1526   maxlength: "maxLength",
  1527   readonly:  "readOnly",
  1528   longdesc:  "longDesc"
  1529 };
  1531 Element._attributeTranslations.values = {
  1532   _getAttr: function(element, attribute) {
  1533     return element.getAttribute(attribute, 2);
  1534   },
  1536   _flag: function(element, attribute) {
  1537     return $(element).hasAttribute(attribute) ? attribute : null;
  1538   },
  1540   style: function(element) {
  1541     return element.style.cssText.toLowerCase();
  1542   },
  1544   title: function(element) {
  1545     var node = element.getAttributeNode('title');
  1546     return node.specified ? node.nodeValue : null;
  1548 };
  1550 Object.extend(Element._attributeTranslations.values, {
  1551   href: Element._attributeTranslations.values._getAttr,
  1552   src:  Element._attributeTranslations.values._getAttr,
  1553   disabled: Element._attributeTranslations.values._flag,
  1554   checked:  Element._attributeTranslations.values._flag,
  1555   readonly: Element._attributeTranslations.values._flag,
  1556   multiple: Element._attributeTranslations.values._flag
  1557 });
  1559 Element.Methods.Simulated = {
  1560   hasAttribute: function(element, attribute) {
  1561     var t = Element._attributeTranslations;
  1562     attribute = t.names[attribute] || attribute;
  1563     return $(element).getAttributeNode(attribute).specified;
  1565 };
  1567 // IE is missing .innerHTML support for TABLE-related elements
  1568 if (document.all && !window.opera){
  1569   Element.Methods.update = function(element, html) {
  1570     element = $(element);
  1571     html = typeof html == 'undefined' ? '' : html.toString();
  1572     var tagName = element.tagName.toUpperCase();
  1573     if (['THEAD','TBODY','TR','TD'].include(tagName)) {
  1574       var div = document.createElement('div');
  1575       switch (tagName) {
  1576         case 'THEAD':
  1577         case 'TBODY':
  1578           div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
  1579           depth = 2;
  1580           break;
  1581         case 'TR':
  1582           div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
  1583           depth = 3;
  1584           break;
  1585         case 'TD':
  1586           div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
  1587           depth = 4;
  1589       $A(element.childNodes).each(function(node){
  1590         element.removeChild(node)
  1591       });
  1592       depth.times(function(){ div = div.firstChild });
  1594       $A(div.childNodes).each(
  1595         function(node){ element.appendChild(node) });
  1596     } else {
  1597       element.innerHTML = html.stripScripts();
  1599     setTimeout(function() {html.evalScripts()}, 10);
  1600     return element;
  1602 };
  1604 Object.extend(Element, Element.Methods);
  1606 var _nativeExtensions = false;
  1608 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
  1609   ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
  1610     var className = 'HTML' + tag + 'Element';
  1611     if(window[className]) return;
  1612     var klass = window[className] = {};
  1613     klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
  1614   });
  1616 Element.addMethods = function(methods) {
  1617   Object.extend(Element.Methods, methods || {});
  1619   function copy(methods, destination, onlyIfAbsent) {
  1620     onlyIfAbsent = onlyIfAbsent || false;
  1621     var cache = Element.extend.cache;
  1622     for (var property in methods) {
  1623       var value = methods[property];
  1624       if (!onlyIfAbsent || !(property in destination))
  1625         destination[property] = cache.findOrStore(value);
  1629   if (typeof HTMLElement != 'undefined') {
  1630     copy(Element.Methods, HTMLElement.prototype);
  1631     copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  1632     copy(Form.Methods, HTMLFormElement.prototype);
  1633     [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
  1634       copy(Form.Element.Methods, klass.prototype);
  1635     });
  1636     _nativeExtensions = true;
  1640 var Toggle = new Object();
  1641 Toggle.display = Element.toggle;
  1643 /*--------------------------------------------------------------------------*/
  1645 Abstract.Insertion = function(adjacency) {
  1646   this.adjacency = adjacency;
  1649 Abstract.Insertion.prototype = {
  1650   initialize: function(element, content) {
  1651     this.element = $(element);
  1652     this.content = content.stripScripts();
  1654     if (this.adjacency && this.element.insertAdjacentHTML) {
  1655       try {
  1656         this.element.insertAdjacentHTML(this.adjacency, this.content);
  1657       } catch (e) {
  1658         var tagName = this.element.tagName.toUpperCase();
  1659         if (['TBODY', 'TR'].include(tagName)) {
  1660           this.insertContent(this.contentFromAnonymousTable());
  1661         } else {
  1662           throw e;
  1665     } else {
  1666       this.range = this.element.ownerDocument.createRange();
  1667       if (this.initializeRange) this.initializeRange();
  1668       this.insertContent([this.range.createContextualFragment(this.content)]);
  1671     setTimeout(function() {content.evalScripts()}, 10);
  1672   },
  1674   contentFromAnonymousTable: function() {
  1675     var div = document.createElement('div');
  1676     div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
  1677     return $A(div.childNodes[0].childNodes[0].childNodes);
  1681 var Insertion = new Object();
  1683 Insertion.Before = Class.create();
  1684 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  1685   initializeRange: function() {
  1686     this.range.setStartBefore(this.element);
  1687   },
  1689   insertContent: function(fragments) {
  1690     fragments.each((function(fragment) {
  1691       this.element.parentNode.insertBefore(fragment, this.element);
  1692     }).bind(this));
  1694 });
  1696 Insertion.Top = Class.create();
  1697 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  1698   initializeRange: function() {
  1699     this.range.selectNodeContents(this.element);
  1700     this.range.collapse(true);
  1701   },
  1703   insertContent: function(fragments) {
  1704     fragments.reverse(false).each((function(fragment) {
  1705       this.element.insertBefore(fragment, this.element.firstChild);
  1706     }).bind(this));
  1708 });
  1710 Insertion.Bottom = Class.create();
  1711 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  1712   initializeRange: function() {
  1713     this.range.selectNodeContents(this.element);
  1714     this.range.collapse(this.element);
  1715   },
  1717   insertContent: function(fragments) {
  1718     fragments.each((function(fragment) {
  1719       this.element.appendChild(fragment);
  1720     }).bind(this));
  1722 });
  1724 Insertion.After = Class.create();
  1725 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  1726   initializeRange: function() {
  1727     this.range.setStartAfter(this.element);
  1728   },
  1730   insertContent: function(fragments) {
  1731     fragments.each((function(fragment) {
  1732       this.element.parentNode.insertBefore(fragment,
  1733         this.element.nextSibling);
  1734     }).bind(this));
  1736 });
  1738 /*--------------------------------------------------------------------------*/
  1740 Element.ClassNames = Class.create();
  1741 Element.ClassNames.prototype = {
  1742   initialize: function(element) {
  1743     this.element = $(element);
  1744   },
  1746   _each: function(iterator) {
  1747     this.element.className.split(/\s+/).select(function(name) {
  1748       return name.length > 0;
  1749     })._each(iterator);
  1750   },
  1752   set: function(className) {
  1753     this.element.className = className;
  1754   },
  1756   add: function(classNameToAdd) {
  1757     if (this.include(classNameToAdd)) return;
  1758     this.set($A(this).concat(classNameToAdd).join(' '));
  1759   },
  1761   remove: function(classNameToRemove) {
  1762     if (!this.include(classNameToRemove)) return;
  1763     this.set($A(this).without(classNameToRemove).join(' '));
  1764   },
  1766   toString: function() {
  1767     return $A(this).join(' ');
  1769 };
  1771 Object.extend(Element.ClassNames.prototype, Enumerable);
  1772 var Selector = Class.create();
  1773 Selector.prototype = {
  1774   initialize: function(expression) {
  1775     this.params = {classNames: []};
  1776     this.expression = expression.toString().strip();
  1777     this.parseExpression();
  1778     this.compileMatcher();
  1779   },
  1781   parseExpression: function() {
  1782     function abort(message) { throw 'Parse error in selector: ' + message; }
  1784     if (this.expression == '')  abort('empty expression');
  1786     var params = this.params, expr = this.expression, match, modifier, clause, rest;
  1787     while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
  1788       params.attributes = params.attributes || [];
  1789       params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
  1790       expr = match[1];
  1793     if (expr == '*') return this.params.wildcard = true;
  1795     while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
  1796       modifier = match[1], clause = match[2], rest = match[3];
  1797       switch (modifier) {
  1798         case '#':       params.id = clause; break;
  1799         case '.':       params.classNames.push(clause); break;
  1800         case '':
  1801         case undefined: params.tagName = clause.toUpperCase(); break;
  1802         default:        abort(expr.inspect());
  1804       expr = rest;
  1807     if (expr.length > 0) abort(expr.inspect());
  1808   },
  1810   buildMatchExpression: function() {
  1811     var params = this.params, conditions = [], clause;
  1813     if (params.wildcard)
  1814       conditions.push('true');
  1815     if (clause = params.id)
  1816       conditions.push('element.readAttribute("id") == ' + clause.inspect());
  1817     if (clause = params.tagName)
  1818       conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
  1819     if ((clause = params.classNames).length > 0)
  1820       for (var i = 0, length = clause.length; i < length; i++)
  1821         conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
  1822     if (clause = params.attributes) {
  1823       clause.each(function(attribute) {
  1824         var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
  1825         var splitValueBy = function(delimiter) {
  1826           return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
  1829         switch (attribute.operator) {
  1830           case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
  1831           case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
  1832           case '|=':      conditions.push(
  1833                             splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
  1834                           ); break;
  1835           case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
  1836           case '':
  1837           case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
  1838           default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
  1840       });
  1843     return conditions.join(' && ');
  1844   },
  1846   compileMatcher: function() {
  1847     this.match = new Function('element', 'if (!element.tagName) return false; \
  1848       element = $(element); \
  1849       return ' + this.buildMatchExpression());
  1850   },
  1852   findElements: function(scope) {
  1853     var element;
  1855     if (element = $(this.params.id))
  1856       if (this.match(element))
  1857         if (!scope || Element.childOf(element, scope))
  1858           return [element];
  1860     scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
  1862     var results = [];
  1863     for (var i = 0, length = scope.length; i < length; i++)
  1864       if (this.match(element = scope[i]))
  1865         results.push(Element.extend(element));
  1867     return results;
  1868   },
  1870   toString: function() {
  1871     return this.expression;
  1875 Object.extend(Selector, {
  1876   matchElements: function(elements, expression) {
  1877     var selector = new Selector(expression);
  1878     return elements.select(selector.match.bind(selector)).map(Element.extend);
  1879   },
  1881   findElement: function(elements, expression, index) {
  1882     if (typeof expression == 'number') index = expression, expression = false;
  1883     return Selector.matchElements(elements, expression || '*')[index || 0];
  1884   },
  1886   findChildElements: function(element, expressions) {
  1887     return expressions.map(function(expression) {
  1888       return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
  1889         var selector = new Selector(expr);
  1890         return results.inject([], function(elements, result) {
  1891           return elements.concat(selector.findElements(result || element));
  1892         });
  1893       });
  1894     }).flatten();
  1896 });
  1898 function $$() {
  1899   return Selector.findChildElements(document, $A(arguments));
  1901 var Form = {
  1902   reset: function(form) {
  1903     $(form).reset();
  1904     return form;
  1905   },
  1907   serializeElements: function(elements, getHash) {
  1908     var data = elements.inject({}, function(result, element) {
  1909       if (!element.disabled && element.name) {
  1910         var key = element.name, value = $(element).getValue();
  1911         if (value != undefined) {
  1912           if (result[key]) {
  1913             if (result[key].constructor != Array) result[key] = [result[key]];
  1914             result[key].push(value);
  1916           else result[key] = value;
  1919       return result;
  1920     });
  1922     return getHash ? data : Hash.toQueryString(data);
  1924 };
  1926 Form.Methods = {
  1927   serialize: function(form, getHash) {
  1928     return Form.serializeElements(Form.getElements(form), getHash);
  1929   },
  1931   getElements: function(form) {
  1932     return $A($(form).getElementsByTagName('*')).inject([],
  1933       function(elements, child) {
  1934         if (Form.Element.Serializers[child.tagName.toLowerCase()])
  1935           elements.push(Element.extend(child));
  1936         return elements;
  1938     );
  1939   },
  1941   getInputs: function(form, typeName, name) {
  1942     form = $(form);
  1943     var inputs = form.getElementsByTagName('input');
  1945     if (!typeName && !name) return $A(inputs).map(Element.extend);
  1947     for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
  1948       var input = inputs[i];
  1949       if ((typeName && input.type != typeName) || (name && input.name != name))
  1950         continue;
  1951       matchingInputs.push(Element.extend(input));
  1954     return matchingInputs;
  1955   },
  1957   disable: function(form) {
  1958     form = $(form);
  1959     form.getElements().each(function(element) {
  1960       element.blur();
  1961       element.disabled = 'true';
  1962     });
  1963     return form;
  1964   },
  1966   enable: function(form) {
  1967     form = $(form);
  1968     form.getElements().each(function(element) {
  1969       element.disabled = '';
  1970     });
  1971     return form;
  1972   },
  1974   findFirstElement: function(form) {
  1975     return $(form).getElements().find(function(element) {
  1976       return element.type != 'hidden' && !element.disabled &&
  1977         ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
  1978     });
  1979   },
  1981   focusFirstElement: function(form) {
  1982     form = $(form);
  1983     form.findFirstElement().activate();
  1984     return form;
  1988 Object.extend(Form, Form.Methods);
  1990 /*--------------------------------------------------------------------------*/
  1992 Form.Element = {
  1993   focus: function(element) {
  1994     $(element).focus();
  1995     return element;
  1996   },
  1998   select: function(element) {
  1999     $(element).select();
  2000     return element;
  2004 Form.Element.Methods = {
  2005   serialize: function(element) {
  2006     element = $(element);
  2007     if (!element.disabled && element.name) {
  2008       var value = element.getValue();
  2009       if (value != undefined) {
  2010         var pair = {};
  2011         pair[element.name] = value;
  2012         return Hash.toQueryString(pair);
  2015     return '';
  2016   },
  2018   getValue: function(element) {
  2019     element = $(element);
  2020     var method = element.tagName.toLowerCase();
  2021     return Form.Element.Serializers[method](element);
  2022   },
  2024   clear: function(element) {
  2025     $(element).value = '';
  2026     return element;
  2027   },
  2029   present: function(element) {
  2030     return $(element).value != '';
  2031   },
  2033   activate: function(element) {
  2034     element = $(element);
  2035     element.focus();
  2036     if (element.select && ( element.tagName.toLowerCase() != 'input' ||
  2037       !['button', 'reset', 'submit'].include(element.type) ) )
  2038       element.select();
  2039     return element;
  2040   },
  2042   disable: function(element) {
  2043     element = $(element);
  2044     element.disabled = true;
  2045     return element;
  2046   },
  2048   enable: function(element) {
  2049     element = $(element);
  2050     element.blur();
  2051     element.disabled = false;
  2052     return element;
  2056 Object.extend(Form.Element, Form.Element.Methods);
  2057 var Field = Form.Element;
  2058 var $F = Form.Element.getValue;
  2060 /*--------------------------------------------------------------------------*/
  2062 Form.Element.Serializers = {
  2063   input: function(element) {
  2064     switch (element.type.toLowerCase()) {
  2065       case 'checkbox':
  2066       case 'radio':
  2067         return Form.Element.Serializers.inputSelector(element);
  2068       default:
  2069         return Form.Element.Serializers.textarea(element);
  2071   },
  2073   inputSelector: function(element) {
  2074     return element.checked ? element.value : null;
  2075   },
  2077   textarea: function(element) {
  2078     return element.value;
  2079   },
  2081   select: function(element) {
  2082     return this[element.type == 'select-one' ?
  2083       'selectOne' : 'selectMany'](element);
  2084   },
  2086   selectOne: function(element) {
  2087     var index = element.selectedIndex;
  2088     return index >= 0 ? this.optionValue(element.options[index]) : null;
  2089   },
  2091   selectMany: function(element) {
  2092     var values, length = element.length;
  2093     if (!length) return null;
  2095     for (var i = 0, values = []; i < length; i++) {
  2096       var opt = element.options[i];
  2097       if (opt.selected) values.push(this.optionValue(opt));
  2099     return values;
  2100   },
  2102   optionValue: function(opt) {
  2103     // extend element because hasAttribute may not be native
  2104     return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  2108 /*--------------------------------------------------------------------------*/
  2110 Abstract.TimedObserver = function() {}
  2111 Abstract.TimedObserver.prototype = {
  2112   initialize: function(element, frequency, callback) {
  2113     this.frequency = frequency;
  2114     this.element   = $(element);
  2115     this.callback  = callback;
  2117     this.lastValue = this.getValue();
  2118     this.registerCallback();
  2119   },
  2121   registerCallback: function() {
  2122     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  2123   },
  2125   onTimerEvent: function() {
  2126     var value = this.getValue();
  2127     var changed = ('string' == typeof this.lastValue && 'string' == typeof value
  2128       ? this.lastValue != value : String(this.lastValue) != String(value));
  2129     if (changed) {
  2130       this.callback(this.element, value);
  2131       this.lastValue = value;
  2136 Form.Element.Observer = Class.create();
  2137 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  2138   getValue: function() {
  2139     return Form.Element.getValue(this.element);
  2141 });
  2143 Form.Observer = Class.create();
  2144 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  2145   getValue: function() {
  2146     return Form.serialize(this.element);
  2148 });
  2150 /*--------------------------------------------------------------------------*/
  2152 Abstract.EventObserver = function() {}
  2153 Abstract.EventObserver.prototype = {
  2154   initialize: function(element, callback) {
  2155     this.element  = $(element);
  2156     this.callback = callback;
  2158     this.lastValue = this.getValue();
  2159     if (this.element.tagName.toLowerCase() == 'form')
  2160       this.registerFormCallbacks();
  2161     else
  2162       this.registerCallback(this.element);
  2163   },
  2165   onElementEvent: function() {
  2166     var value = this.getValue();
  2167     if (this.lastValue != value) {
  2168       this.callback(this.element, value);
  2169       this.lastValue = value;
  2171   },
  2173   registerFormCallbacks: function() {
  2174     Form.getElements(this.element).each(this.registerCallback.bind(this));
  2175   },
  2177   registerCallback: function(element) {
  2178     if (element.type) {
  2179       switch (element.type.toLowerCase()) {
  2180         case 'checkbox':
  2181         case 'radio':
  2182           Event.observe(element, 'click', this.onElementEvent.bind(this));
  2183           break;
  2184         default:
  2185           Event.observe(element, 'change', this.onElementEvent.bind(this));
  2186           break;
  2192 Form.Element.EventObserver = Class.create();
  2193 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  2194   getValue: function() {
  2195     return Form.Element.getValue(this.element);
  2197 });
  2199 Form.EventObserver = Class.create();
  2200 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  2201   getValue: function() {
  2202     return Form.serialize(this.element);
  2204 });
  2205 if (!window.Event) {
  2206   var Event = new Object();
  2209 Object.extend(Event, {
  2210   KEY_BACKSPACE: 8,
  2211   KEY_TAB:       9,
  2212   KEY_RETURN:   13,
  2213   KEY_ESC:      27,
  2214   KEY_LEFT:     37,
  2215   KEY_UP:       38,
  2216   KEY_RIGHT:    39,
  2217   KEY_DOWN:     40,
  2218   KEY_DELETE:   46,
  2219   KEY_HOME:     36,
  2220   KEY_END:      35,
  2221   KEY_PAGEUP:   33,
  2222   KEY_PAGEDOWN: 34,
  2224   element: function(event) {
  2225     return event.target || event.srcElement;
  2226   },
  2228   isLeftClick: function(event) {
  2229     return (((event.which) && (event.which == 1)) ||
  2230             ((event.button) && (event.button == 1)));
  2231   },
  2233   pointerX: function(event) {
  2234     return event.pageX || (event.clientX +
  2235       (document.documentElement.scrollLeft || document.body.scrollLeft));
  2236   },
  2238   pointerY: function(event) {
  2239     return event.pageY || (event.clientY +
  2240       (document.documentElement.scrollTop || document.body.scrollTop));
  2241   },
  2243   stop: function(event) {
  2244     if (event.preventDefault) {
  2245       event.preventDefault();
  2246       event.stopPropagation();
  2247     } else {
  2248       event.returnValue = false;
  2249       event.cancelBubble = true;
  2251   },
  2253   // find the first node with the given tagName, starting from the
  2254   // node the event was triggered on; traverses the DOM upwards
  2255   findElement: function(event, tagName) {
  2256     var element = Event.element(event);
  2257     while (element.parentNode && (!element.tagName ||
  2258         (element.tagName.toUpperCase() != tagName.toUpperCase())))
  2259       element = element.parentNode;
  2260     return element;
  2261   },
  2263   observers: false,
  2265   _observeAndCache: function(element, name, observer, useCapture) {
  2266     if (!this.observers) this.observers = [];
  2267     if (element.addEventListener) {
  2268       this.observers.push([element, name, observer, useCapture]);
  2269       element.addEventListener(name, observer, useCapture);
  2270     } else if (element.attachEvent) {
  2271       this.observers.push([element, name, observer, useCapture]);
  2272       element.attachEvent('on' + name, observer);
  2274   },
  2276   unloadCache: function() {
  2277     if (!Event.observers) return;
  2278     for (var i = 0, length = Event.observers.length; i < length; i++) {
  2279       Event.stopObserving.apply(this, Event.observers[i]);
  2280       Event.observers[i][0] = null;
  2282     Event.observers = false;
  2283   },
  2285   observe: function(element, name, observer, useCapture) {
  2286     element = $(element);
  2287     useCapture = useCapture || false;
  2289     if (name == 'keypress' &&
  2290         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  2291         || element.attachEvent))
  2292       name = 'keydown';
  2294     Event._observeAndCache(element, name, observer, useCapture);
  2295   },
  2297   stopObserving: function(element, name, observer, useCapture) {
  2298     element = $(element);
  2299     useCapture = useCapture || false;
  2301     if (name == 'keypress' &&
  2302         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  2303         || element.detachEvent))
  2304       name = 'keydown';
  2306     if (element.removeEventListener) {
  2307       element.removeEventListener(name, observer, useCapture);
  2308     } else if (element.detachEvent) {
  2309       try {
  2310         element.detachEvent('on' + name, observer);
  2311       } catch (e) {}
  2314 });
  2316 /* prevent memory leaks in IE */
  2317 if (navigator.appVersion.match(/\bMSIE\b/))
  2318   Event.observe(window, 'unload', Event.unloadCache, false);
  2319 var Position = {
  2320   // set to true if needed, warning: firefox performance problems
  2321   // NOT neeeded for page scrolling, only if draggable contained in
  2322   // scrollable elements
  2323   includeScrollOffsets: false,
  2325   // must be called before calling withinIncludingScrolloffset, every time the
  2326   // page is scrolled
  2327   prepare: function() {
  2328     this.deltaX =  window.pageXOffset
  2329                 || document.documentElement.scrollLeft
  2330                 || document.body.scrollLeft
  2331                 || 0;
  2332     this.deltaY =  window.pageYOffset
  2333                 || document.documentElement.scrollTop
  2334                 || document.body.scrollTop
  2335                 || 0;
  2336   },
  2338   realOffset: function(element) {
  2339     var valueT = 0, valueL = 0;
  2340     do {
  2341       valueT += element.scrollTop  || 0;
  2342       valueL += element.scrollLeft || 0;
  2343       element = element.parentNode;
  2344     } while (element);
  2345     return [valueL, valueT];
  2346   },
  2348   cumulativeOffset: function(element) {
  2349     var valueT = 0, valueL = 0;
  2350     do {
  2351       valueT += element.offsetTop  || 0;
  2352       valueL += element.offsetLeft || 0;
  2353       element = element.offsetParent;
  2354     } while (element);
  2355     return [valueL, valueT];
  2356   },
  2358   positionedOffset: function(element) {
  2359     var valueT = 0, valueL = 0;
  2360     do {
  2361       valueT += element.offsetTop  || 0;
  2362       valueL += element.offsetLeft || 0;
  2363       element = element.offsetParent;
  2364       if (element) {
  2365         if(element.tagName=='BODY') break;
  2366         var p = Element.getStyle(element, 'position');
  2367         if (p == 'relative' || p == 'absolute') break;
  2369     } while (element);
  2370     return [valueL, valueT];
  2371   },
  2373   offsetParent: function(element) {
  2374     if (element.offsetParent) return element.offsetParent;
  2375     if (element == document.body) return element;
  2377     while ((element = element.parentNode) && element != document.body)
  2378       if (Element.getStyle(element, 'position') != 'static')
  2379         return element;
  2381     return document.body;
  2382   },
  2384   // caches x/y coordinate pair to use with overlap
  2385   within: function(element, x, y) {
  2386     if (this.includeScrollOffsets)
  2387       return this.withinIncludingScrolloffsets(element, x, y);
  2388     this.xcomp = x;
  2389     this.ycomp = y;
  2390     this.offset = this.cumulativeOffset(element);
  2392     return (y >= this.offset[1] &&
  2393             y <  this.offset[1] + element.offsetHeight &&
  2394             x >= this.offset[0] &&
  2395             x <  this.offset[0] + element.offsetWidth);
  2396   },
  2398   withinIncludingScrolloffsets: function(element, x, y) {
  2399     var offsetcache = this.realOffset(element);
  2401     this.xcomp = x + offsetcache[0] - this.deltaX;
  2402     this.ycomp = y + offsetcache[1] - this.deltaY;
  2403     this.offset = this.cumulativeOffset(element);
  2405     return (this.ycomp >= this.offset[1] &&
  2406             this.ycomp <  this.offset[1] + element.offsetHeight &&
  2407             this.xcomp >= this.offset[0] &&
  2408             this.xcomp <  this.offset[0] + element.offsetWidth);
  2409   },
  2411   // within must be called directly before
  2412   overlap: function(mode, element) {
  2413     if (!mode) return 0;
  2414     if (mode == 'vertical')
  2415       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
  2416         element.offsetHeight;
  2417     if (mode == 'horizontal')
  2418       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
  2419         element.offsetWidth;
  2420   },
  2422   page: function(forElement) {
  2423     var valueT = 0, valueL = 0;
  2425     var element = forElement;
  2426     do {
  2427       valueT += element.offsetTop  || 0;
  2428       valueL += element.offsetLeft || 0;
  2430       // Safari fix
  2431       if (element.offsetParent==document.body)
  2432         if (Element.getStyle(element,'position')=='absolute') break;
  2434     } while (element = element.offsetParent);
  2436     element = forElement;
  2437     do {
  2438       if (!window.opera || element.tagName=='BODY') {
  2439         valueT -= element.scrollTop  || 0;
  2440         valueL -= element.scrollLeft || 0;
  2442     } while (element = element.parentNode);
  2444     return [valueL, valueT];
  2445   },
  2447   clone: function(source, target) {
  2448     var options = Object.extend({
  2449       setLeft:    true,
  2450       setTop:     true,
  2451       setWidth:   true,
  2452       setHeight:  true,
  2453       offsetTop:  0,
  2454       offsetLeft: 0
  2455     }, arguments[2] || {})
  2457     // find page position of source
  2458     source = $(source);
  2459     var p = Position.page(source);
  2461     // find coordinate system to use
  2462     target = $(target);
  2463     var delta = [0, 0];
  2464     var parent = null;
  2465     // delta [0,0] will do fine with position: fixed elements,
  2466     // position:absolute needs offsetParent deltas
  2467     if (Element.getStyle(target,'position') == 'absolute') {
  2468       parent = Position.offsetParent(target);
  2469       delta = Position.page(parent);
  2472     // correct by body offsets (fixes Safari)
  2473     if (parent == document.body) {
  2474       delta[0] -= document.body.offsetLeft;
  2475       delta[1] -= document.body.offsetTop;
  2478     // set position
  2479     if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
  2480     if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
  2481     if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
  2482     if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  2483   },
  2485   absolutize: function(element) {
  2486     element = $(element);
  2487     if (element.style.position == 'absolute') return;
  2488     Position.prepare();
  2490     var offsets = Position.positionedOffset(element);
  2491     var top     = offsets[1];
  2492     var left    = offsets[0];
  2493     var width   = element.clientWidth;
  2494     var height  = element.clientHeight;
  2496     element._originalLeft   = left - parseFloat(element.style.left  || 0);
  2497     element._originalTop    = top  - parseFloat(element.style.top || 0);
  2498     element._originalWidth  = element.style.width;
  2499     element._originalHeight = element.style.height;
  2501     element.style.position = 'absolute';
  2502     element.style.top    = top + 'px';
  2503     element.style.left   = left + 'px';
  2504     element.style.width  = width + 'px';
  2505     element.style.height = height + 'px';
  2506   },
  2508   relativize: function(element) {
  2509     element = $(element);
  2510     if (element.style.position == 'relative') return;
  2511     Position.prepare();
  2513     element.style.position = 'relative';
  2514     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
  2515     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
  2517     element.style.top    = top + 'px';
  2518     element.style.left   = left + 'px';
  2519     element.style.height = element._originalHeight;
  2520     element.style.width  = element._originalWidth;
  2524 // Safari returns margins on body which is incorrect if the child is absolutely
  2525 // positioned.  For performance reasons, redefine Position.cumulativeOffset for
  2526 // KHTML/WebKit only.
  2527 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  2528   Position.cumulativeOffset = function(element) {
  2529     var valueT = 0, valueL = 0;
  2530     do {
  2531       valueT += element.offsetTop  || 0;
  2532       valueL += element.offsetLeft || 0;
  2533       if (element.offsetParent == document.body)
  2534         if (Element.getStyle(element, 'position') == 'absolute') break;
  2536       element = element.offsetParent;
  2537     } while (element);
  2539     return [valueL, valueT];
  2543 Element.addMethods();
  2546 // ------------------------------------------------------------------------
  2547 // ------------------------------------------------------------------------
  2549 // The rest of this file is the actual ray tracer written by Adam
  2550 // Burmister. It's a concatenation of the following files:
  2551 //
  2552 //   flog/color.js
  2553 //   flog/light.js
  2554 //   flog/vector.js
  2555 //   flog/ray.js
  2556 //   flog/scene.js
  2557 //   flog/material/basematerial.js
  2558 //   flog/material/solid.js
  2559 //   flog/material/chessboard.js
  2560 //   flog/shape/baseshape.js
  2561 //   flog/shape/sphere.js
  2562 //   flog/shape/plane.js
  2563 //   flog/intersectioninfo.js
  2564 //   flog/camera.js
  2565 //   flog/background.js
  2566 //   flog/engine.js
  2569 /* Fake a Flog.* namespace */
  2570 if(typeof(Flog) == 'undefined') var Flog = {};
  2571 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2573 Flog.RayTracer.Color = Class.create();
  2575 Flog.RayTracer.Color.prototype = {
  2576     red : 0.0,
  2577     green : 0.0,
  2578     blue : 0.0,
  2580     initialize : function(r, g, b) {
  2581         if(!r) r = 0.0;
  2582         if(!g) g = 0.0;
  2583         if(!b) b = 0.0;
  2585         this.red = r;
  2586         this.green = g;
  2587         this.blue = b;
  2588     },
  2590     add : function(c1, c2){
  2591         var result = new Flog.RayTracer.Color(0,0,0);
  2593         result.red = c1.red + c2.red;
  2594         result.green = c1.green + c2.green;
  2595         result.blue = c1.blue + c2.blue;
  2597         return result;
  2598     },
  2600     addScalar: function(c1, s){
  2601         var result = new Flog.RayTracer.Color(0,0,0);
  2603         result.red = c1.red + s;
  2604         result.green = c1.green + s;
  2605         result.blue = c1.blue + s;
  2607         result.limit();
  2609         return result;
  2610     },
  2612     subtract: function(c1, c2){
  2613         var result = new Flog.RayTracer.Color(0,0,0);
  2615         result.red = c1.red - c2.red;
  2616         result.green = c1.green - c2.green;
  2617         result.blue = c1.blue - c2.blue;
  2619         return result;
  2620     },
  2622     multiply : function(c1, c2) {
  2623         var result = new Flog.RayTracer.Color(0,0,0);
  2625         result.red = c1.red * c2.red;
  2626         result.green = c1.green * c2.green;
  2627         result.blue = c1.blue * c2.blue;
  2629         return result;
  2630     },
  2632     multiplyScalar : function(c1, f) {
  2633         var result = new Flog.RayTracer.Color(0,0,0);
  2635         result.red = c1.red * f;
  2636         result.green = c1.green * f;
  2637         result.blue = c1.blue * f;
  2639         return result;
  2640     },
  2642     divideFactor : function(c1, f) {
  2643         var result = new Flog.RayTracer.Color(0,0,0);
  2645         result.red = c1.red / f;
  2646         result.green = c1.green / f;
  2647         result.blue = c1.blue / f;
  2649         return result;
  2650     },
  2652     limit: function(){
  2653         this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0;
  2654         this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0;
  2655         this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0;
  2656     },
  2658     distance : function(color) {
  2659         var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue);
  2660         return d;
  2661     },
  2663     blend: function(c1, c2, w){
  2664         var result = new Flog.RayTracer.Color(0,0,0);
  2665         result = Flog.RayTracer.Color.prototype.add(
  2666                     Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w),
  2667                     Flog.RayTracer.Color.prototype.multiplyScalar(c2, w)
  2668                   );
  2669         return result;
  2670     },
  2672     toString : function () {
  2673         var r = Math.floor(this.red*255);
  2674         var g = Math.floor(this.green*255);
  2675         var b = Math.floor(this.blue*255);
  2677         return "rgb("+ r +","+ g +","+ b +")";
  2680 /* Fake a Flog.* namespace */
  2681 if(typeof(Flog) == 'undefined') var Flog = {};
  2682 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2684 Flog.RayTracer.Light = Class.create();
  2686 Flog.RayTracer.Light.prototype = {
  2687     position: null,
  2688     color: null,
  2689     intensity: 10.0,
  2691     initialize : function(pos, color, intensity) {
  2692         this.position = pos;
  2693         this.color = color;
  2694         this.intensity = (intensity ? intensity : 10.0);
  2695     },
  2697     getIntensity: function(distance){
  2698         if(distance >= intensity) return 0;
  2700         return Math.pow((intensity - distance) / strength, 0.2);
  2701     },
  2703     toString : function () {
  2704         return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
  2707 /* Fake a Flog.* namespace */
  2708 if(typeof(Flog) == 'undefined') var Flog = {};
  2709 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2711 Flog.RayTracer.Vector = Class.create();
  2713 Flog.RayTracer.Vector.prototype = {
  2714     x : 0.0,
  2715     y : 0.0,
  2716     z : 0.0,
  2718     initialize : function(x, y, z) {
  2719         this.x = (x ? x : 0);
  2720         this.y = (y ? y : 0);
  2721         this.z = (z ? z : 0);
  2722     },
  2724     copy: function(vector){
  2725         this.x = vector.x;
  2726         this.y = vector.y;
  2727         this.z = vector.z;
  2728     },
  2730     normalize : function() {
  2731         var m = this.magnitude();
  2732         return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
  2733     },
  2735     magnitude : function() {
  2736         return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
  2737     },
  2739     cross : function(w) {
  2740         return new Flog.RayTracer.Vector(
  2741                                             -this.z * w.y + this.y * w.z,
  2742                                            this.z * w.x - this.x * w.z,
  2743                                           -this.y * w.x + this.x * w.y);
  2744     },
  2746     dot : function(w) {
  2747         return this.x * w.x + this.y * w.y + this.z * w.z;
  2748     },
  2750     add : function(v, w) {
  2751         return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
  2752     },
  2754     subtract : function(v, w) {
  2755         if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
  2756         return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
  2757     },
  2759     multiplyVector : function(v, w) {
  2760         return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
  2761     },
  2763     multiplyScalar : function(v, w) {
  2764         return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
  2765     },
  2767     toString : function () {
  2768         return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
  2771 /* Fake a Flog.* namespace */
  2772 if(typeof(Flog) == 'undefined') var Flog = {};
  2773 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2775 Flog.RayTracer.Ray = Class.create();
  2777 Flog.RayTracer.Ray.prototype = {
  2778     position : null,
  2779     direction : null,
  2780     initialize : function(pos, dir) {
  2781         this.position = pos;
  2782         this.direction = dir;
  2783     },
  2785     toString : function () {
  2786         return 'Ray [' + this.position + ',' + this.direction + ']';
  2789 /* Fake a Flog.* namespace */
  2790 if(typeof(Flog) == 'undefined') var Flog = {};
  2791 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2793 Flog.RayTracer.Scene = Class.create();
  2795 Flog.RayTracer.Scene.prototype = {
  2796     camera : null,
  2797     shapes : [],
  2798     lights : [],
  2799     background : null,
  2801     initialize : function() {
  2802         this.camera = new Flog.RayTracer.Camera(
  2803             new Flog.RayTracer.Vector(0,0,-5),
  2804             new Flog.RayTracer.Vector(0,0,1),
  2805             new Flog.RayTracer.Vector(0,1,0)
  2806         );
  2807         this.shapes = new Array();
  2808         this.lights = new Array();
  2809         this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
  2812 /* Fake a Flog.* namespace */
  2813 if(typeof(Flog) == 'undefined') var Flog = {};
  2814 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2815 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
  2817 Flog.RayTracer.Material.BaseMaterial = Class.create();
  2819 Flog.RayTracer.Material.BaseMaterial.prototype = {
  2821     gloss: 2.0,             // [0...infinity] 0 = matt
  2822     transparency: 0.0,      // 0=opaque
  2823     reflection: 0.0,        // [0...infinity] 0 = no reflection
  2824     refraction: 0.50,
  2825     hasTexture: false,
  2827     initialize : function() {
  2829     },
  2831     getColor: function(u, v){
  2833     },
  2835     wrapUp: function(t){
  2836         t = t % 2.0;
  2837         if(t < -1) t += 2.0;
  2838         if(t >= 1) t -= 2.0;
  2839         return t;
  2840     },
  2842     toString : function () {
  2843         return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
  2846 /* Fake a Flog.* namespace */
  2847 if(typeof(Flog) == 'undefined') var Flog = {};
  2848 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2850 Flog.RayTracer.Material.Solid = Class.create();
  2852 Flog.RayTracer.Material.Solid.prototype = Object.extend(
  2853     new Flog.RayTracer.Material.BaseMaterial(), {
  2854         initialize : function(color, reflection, refraction, transparency, gloss) {
  2855             this.color = color;
  2856             this.reflection = reflection;
  2857             this.transparency = transparency;
  2858             this.gloss = gloss;
  2859             this.hasTexture = false;
  2860         },
  2862         getColor: function(u, v){
  2863             return this.color;
  2864         },
  2866         toString : function () {
  2867             return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
  2870 );
  2871 /* Fake a Flog.* namespace */
  2872 if(typeof(Flog) == 'undefined') var Flog = {};
  2873 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2875 Flog.RayTracer.Material.Chessboard = Class.create();
  2877 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
  2878     new Flog.RayTracer.Material.BaseMaterial(), {
  2879         colorEven: null,
  2880         colorOdd: null,
  2881         density: 0.5,
  2883         initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
  2884             this.colorEven = colorEven;
  2885             this.colorOdd = colorOdd;
  2886             this.reflection = reflection;
  2887             this.transparency = transparency;
  2888             this.gloss = gloss;
  2889             this.density = density;
  2890             this.hasTexture = true;
  2891         },
  2893         getColor: function(u, v){
  2894             var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
  2896             if(t < 0.0)
  2897                 return this.colorEven;
  2898             else
  2899                 return this.colorOdd;
  2900         },
  2902         toString : function () {
  2903             return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
  2906 );
  2907 /* Fake a Flog.* namespace */
  2908 if(typeof(Flog) == 'undefined') var Flog = {};
  2909 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2910 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
  2912 Flog.RayTracer.Shape.BaseShape = Class.create();
  2914 Flog.RayTracer.Shape.BaseShape.prototype = {
  2915     position: null,
  2916     material: null,
  2918     initialize : function() {
  2919         this.position = new Vector(0,0,0);
  2920         this.material = new Flog.RayTracer.Material.SolidMaterial(
  2921             new Flog.RayTracer.Color(1,0,1),
  2922             0,
  2923             0,
  2925         );
  2926     },
  2928     toString : function () {
  2929         return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
  2932 /* Fake a Flog.* namespace */
  2933 if(typeof(Flog) == 'undefined') var Flog = {};
  2934 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2935 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
  2937 Flog.RayTracer.Shape.Sphere = Class.create();
  2939 Flog.RayTracer.Shape.Sphere.prototype = {
  2940     initialize : function(pos, radius, material) {
  2941         this.radius = radius;
  2942         this.position = pos;
  2943         this.material = material;
  2944     },
  2946     intersect: function(ray){
  2947         var info = new Flog.RayTracer.IntersectionInfo();
  2948         info.shape = this;
  2950         var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
  2952         var B = dst.dot(ray.direction);
  2953         var C = dst.dot(dst) - (this.radius * this.radius);
  2954         var D = (B * B) - C;
  2956         if(D > 0){ // intersection!
  2957             info.isHit = true;
  2958             info.distance = (-B) - Math.sqrt(D);
  2959             info.position = Flog.RayTracer.Vector.prototype.add(
  2960                                                 ray.position,
  2961                                                 Flog.RayTracer.Vector.prototype.multiplyScalar(
  2962                                                     ray.direction,
  2963                                                     info.distance
  2965                                             );
  2966             info.normal = Flog.RayTracer.Vector.prototype.subtract(
  2967                                             info.position,
  2968                                             this.position
  2969                                         ).normalize();
  2971             info.color = this.material.getColor(0,0);
  2972         } else {
  2973             info.isHit = false;
  2975         return info;
  2976     },
  2978     toString : function () {
  2979         return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
  2982 /* Fake a Flog.* namespace */
  2983 if(typeof(Flog) == 'undefined') var Flog = {};
  2984 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  2985 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
  2987 Flog.RayTracer.Shape.Plane = Class.create();
  2989 Flog.RayTracer.Shape.Plane.prototype = {
  2990     d: 0.0,
  2992     initialize : function(pos, d, material) {
  2993         this.position = pos;
  2994         this.d = d;
  2995         this.material = material;
  2996     },
  2998     intersect: function(ray){
  2999         var info = new Flog.RayTracer.IntersectionInfo();
  3001         var Vd = this.position.dot(ray.direction);
  3002         if(Vd == 0) return info; // no intersection
  3004         var t = -(this.position.dot(ray.position) + this.d) / Vd;
  3005         if(t <= 0) return info;
  3007         info.shape = this;
  3008         info.isHit = true;
  3009         info.position = Flog.RayTracer.Vector.prototype.add(
  3010                                             ray.position,
  3011                                             Flog.RayTracer.Vector.prototype.multiplyScalar(
  3012                                                 ray.direction,
  3015                                         );
  3016         info.normal = this.position;
  3017         info.distance = t;
  3019         if(this.material.hasTexture){
  3020             var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
  3021             var vV = vU.cross(this.position);
  3022             var u = info.position.dot(vU);
  3023             var v = info.position.dot(vV);
  3024             info.color = this.material.getColor(u,v);
  3025         } else {
  3026             info.color = this.material.getColor(0,0);
  3029         return info;
  3030     },
  3032     toString : function () {
  3033         return 'Plane [' + this.position + ', d=' + this.d + ']';
  3036 /* Fake a Flog.* namespace */
  3037 if(typeof(Flog) == 'undefined') var Flog = {};
  3038 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  3040 Flog.RayTracer.IntersectionInfo = Class.create();
  3042 Flog.RayTracer.IntersectionInfo.prototype = {
  3043     isHit: false,
  3044     hitCount: 0,
  3045     shape: null,
  3046     position: null,
  3047     normal: null,
  3048     color: null,
  3049     distance: null,
  3051     initialize : function() {
  3052         this.color = new Flog.RayTracer.Color(0,0,0);
  3053     },
  3055     toString : function () {
  3056         return 'Intersection [' + this.position + ']';
  3059 /* Fake a Flog.* namespace */
  3060 if(typeof(Flog) == 'undefined') var Flog = {};
  3061 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  3063 Flog.RayTracer.Camera = Class.create();
  3065 Flog.RayTracer.Camera.prototype = {
  3066     position: null,
  3067     lookAt: null,
  3068     equator: null,
  3069     up: null,
  3070     screen: null,
  3072     initialize : function(pos, lookAt, up) {
  3073         this.position = pos;
  3074         this.lookAt = lookAt;
  3075         this.up = up;
  3076         this.equator = lookAt.normalize().cross(this.up);
  3077         this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
  3078     },
  3080     getRay: function(vx, vy){
  3081         var pos = Flog.RayTracer.Vector.prototype.subtract(
  3082             this.screen,
  3083             Flog.RayTracer.Vector.prototype.subtract(
  3084                 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
  3085                 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
  3087         );
  3088         pos.y = pos.y * -1;
  3089         var dir = Flog.RayTracer.Vector.prototype.subtract(
  3090             pos,
  3091             this.position
  3092         );
  3094         var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
  3096         return ray;
  3097     },
  3099     toString : function () {
  3100         return 'Ray []';
  3103 /* Fake a Flog.* namespace */
  3104 if(typeof(Flog) == 'undefined') var Flog = {};
  3105 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  3107 Flog.RayTracer.Background = Class.create();
  3109 Flog.RayTracer.Background.prototype = {
  3110     color : null,
  3111     ambience : 0.0,
  3113     initialize : function(color, ambience) {
  3114         this.color = color;
  3115         this.ambience = ambience;
  3118 /* Fake a Flog.* namespace */
  3119 if(typeof(Flog) == 'undefined') var Flog = {};
  3120 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
  3122 Flog.RayTracer.Engine = Class.create();
  3124 Flog.RayTracer.Engine.prototype = {
  3125     canvas: null, /* 2d context we can render to */
  3127     initialize: function(options){
  3128         this.options = Object.extend({
  3129                 canvasHeight: 100,
  3130                 canvasWidth: 100,
  3131                 pixelWidth: 2,
  3132                 pixelHeight: 2,
  3133                 renderDiffuse: false,
  3134                 renderShadows: false,
  3135                 renderHighlights: false,
  3136                 renderReflections: false,
  3137                 rayDepth: 2
  3138             }, options || {});
  3140         this.options.canvasHeight /= this.options.pixelHeight;
  3141         this.options.canvasWidth /= this.options.pixelWidth;
  3143         /* TODO: dynamically include other scripts */
  3144     },
  3146     setPixel: function(x, y, color){
  3147         var pxW, pxH;
  3148         pxW = this.options.pixelWidth;
  3149         pxH = this.options.pixelHeight;
  3151         if (this.canvas) {
  3152           this.canvas.fillStyle = color.toString();
  3153           this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
  3154         } else {
  3155           // print(x * pxW, y * pxH, pxW, pxH);
  3157     },
  3159     renderScene: function(scene, canvas){
  3160         /* Get canvas */
  3161         if (canvas) {
  3162           this.canvas = canvas.getContext("2d");
  3163         } else {
  3164           this.canvas = null;
  3167         var canvasHeight = this.options.canvasHeight;
  3168         var canvasWidth = this.options.canvasWidth;
  3170         for(var y=0; y < canvasHeight; y++){
  3171             for(var x=0; x < canvasWidth; x++){
  3172                 var yp = y * 1.0 / canvasHeight * 2 - 1;
  3173           		var xp = x * 1.0 / canvasWidth * 2 - 1;
  3175           		var ray = scene.camera.getRay(xp, yp);
  3177           		var color = this.getPixelColor(ray, scene);
  3179             	this.setPixel(x, y, color);
  3182     },
  3184     getPixelColor: function(ray, scene){
  3185         var info = this.testIntersection(ray, scene, null);
  3186         if(info.isHit){
  3187             var color = this.rayTrace(info, ray, scene, 0);
  3188             return color;
  3190         return scene.background.color;
  3191     },
  3193     testIntersection: function(ray, scene, exclude){
  3194         var hits = 0;
  3195         var best = new Flog.RayTracer.IntersectionInfo();
  3196         best.distance = 2000;
  3198         for(var i=0; i<scene.shapes.length; i++){
  3199             var shape = scene.shapes[i];
  3201             if(shape != exclude){
  3202                 var info = shape.intersect(ray);
  3203                 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
  3204                     best = info;
  3205                     hits++;
  3209         best.hitCount = hits;
  3210         return best;
  3211     },
  3213     getReflectionRay: function(P,N,V){
  3214         var c1 = -N.dot(V);
  3215         var R1 = Flog.RayTracer.Vector.prototype.add(
  3216             Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
  3218         );
  3219         return new Flog.RayTracer.Ray(P, R1);
  3220     },
  3222     rayTrace: function(info, ray, scene, depth){
  3223         // Calc ambient
  3224         var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
  3225         var oldColor = color;
  3226         var shininess = Math.pow(10, info.shape.material.gloss + 1);
  3228         for(var i=0; i<scene.lights.length; i++){
  3229             var light = scene.lights[i];
  3231             // Calc diffuse lighting
  3232             var v = Flog.RayTracer.Vector.prototype.subtract(
  3233                                 light.position,
  3234                                 info.position
  3235                             ).normalize();
  3237             if(this.options.renderDiffuse){
  3238                 var L = v.dot(info.normal);
  3239                 if(L > 0.0){
  3240                     color = Flog.RayTracer.Color.prototype.add(
  3241                                         color,
  3242                                         Flog.RayTracer.Color.prototype.multiply(
  3243                                             info.color,
  3244                                             Flog.RayTracer.Color.prototype.multiplyScalar(
  3245                                                 light.color,
  3249                                     );
  3253             // The greater the depth the more accurate the colours, but
  3254             // this is exponentially (!) expensive
  3255             if(depth <= this.options.rayDepth){
  3256           // calculate reflection ray
  3257           if(this.options.renderReflections && info.shape.material.reflection > 0)
  3259               var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
  3260               var refl = this.testIntersection(reflectionRay, scene, info.shape);
  3262               if (refl.isHit && refl.distance > 0){
  3263                   refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
  3264               } else {
  3265                   refl.color = scene.background.color;
  3268                   color = Flog.RayTracer.Color.prototype.blend(
  3269                     color,
  3270                     refl.color,
  3271                     info.shape.material.reflection
  3272                   );
  3275                 // Refraction
  3276                 /* TODO */
  3279             /* Render shadows and highlights */
  3281             var shadowInfo = new Flog.RayTracer.IntersectionInfo();
  3283             if(this.options.renderShadows){
  3284                 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
  3286                 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
  3287                 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
  3288                     var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
  3289                     var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
  3290                     color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
  3294       // Phong specular highlights
  3295       if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
  3296         var Lv = Flog.RayTracer.Vector.prototype.subtract(
  3297                             info.shape.position,
  3298                             light.position
  3299                         ).normalize();
  3301         var E = Flog.RayTracer.Vector.prototype.subtract(
  3302                             scene.camera.position,
  3303                             info.shape.position
  3304                         ).normalize();
  3306         var H = Flog.RayTracer.Vector.prototype.subtract(
  3307                             E,
  3308                             Lv
  3309                         ).normalize();
  3311         var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
  3312         color = Flog.RayTracer.Color.prototype.add(
  3313                             Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
  3314                             color
  3315                         );
  3318         color.limit();
  3319         return color;
  3321 };
  3324 function renderScene(){
  3325     var scene = new Flog.RayTracer.Scene();
  3327     scene.camera = new Flog.RayTracer.Camera(
  3328                         new Flog.RayTracer.Vector(0, 0, -15),
  3329                         new Flog.RayTracer.Vector(-0.2, 0, 5),
  3330                         new Flog.RayTracer.Vector(0, 1, 0)
  3331                     );
  3333     scene.background = new Flog.RayTracer.Background(
  3334                                 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
  3335                                 0.4
  3336                             );
  3338     var sphere = new Flog.RayTracer.Shape.Sphere(
  3339         new Flog.RayTracer.Vector(-1.5, 1.5, 2),
  3340         1.5,
  3341         new Flog.RayTracer.Material.Solid(
  3342             new Flog.RayTracer.Color(0,0.5,0.5),
  3343             0.3,
  3344             0.0,
  3345             0.0,
  3346             2.0
  3348     );
  3350     var sphere1 = new Flog.RayTracer.Shape.Sphere(
  3351         new Flog.RayTracer.Vector(1, 0.25, 1),
  3352         0.5,
  3353         new Flog.RayTracer.Material.Solid(
  3354             new Flog.RayTracer.Color(0.9,0.9,0.9),
  3355             0.1,
  3356             0.0,
  3357             0.0,
  3358             1.5
  3360     );
  3362     var plane = new Flog.RayTracer.Shape.Plane(
  3363                                 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
  3364                                 1.2,
  3365                                 new Flog.RayTracer.Material.Chessboard(
  3366                                     new Flog.RayTracer.Color(1,1,1),
  3367                                     new Flog.RayTracer.Color(0,0,0),
  3368                                     0.2,
  3369                                     0.0,
  3370                                     1.0,
  3371                                     0.7
  3373                             );
  3375     scene.shapes.push(plane);
  3376     scene.shapes.push(sphere);
  3377     scene.shapes.push(sphere1);
  3379     var light = new Flog.RayTracer.Light(
  3380         new Flog.RayTracer.Vector(5, 10, -1),
  3381         new Flog.RayTracer.Color(0.8, 0.8, 0.8)
  3382     );
  3384     var light1 = new Flog.RayTracer.Light(
  3385         new Flog.RayTracer.Vector(-3, 5, -15),
  3386         new Flog.RayTracer.Color(0.8, 0.8, 0.8),
  3387         100
  3388     );
  3390     scene.lights.push(light);
  3391     scene.lights.push(light1);
  3393     var imageWidth = 100; // $F('imageWidth');
  3394     var imageHeight = 100; // $F('imageHeight');
  3395     var pixelSize = "5,5".split(','); //  $F('pixelSize').split(',');
  3396     var renderDiffuse = true; // $F('renderDiffuse');
  3397     var renderShadows = true; // $F('renderShadows');
  3398     var renderHighlights = true; // $F('renderHighlights');
  3399     var renderReflections = true; // $F('renderReflections');
  3400     var rayDepth = 2;//$F('rayDepth');
  3402     var raytracer = new Flog.RayTracer.Engine(
  3404             canvasWidth: imageWidth,
  3405             canvasHeight: imageHeight,
  3406             pixelWidth: pixelSize[0],
  3407             pixelHeight: pixelSize[1],
  3408             "renderDiffuse": renderDiffuse,
  3409             "renderHighlights": renderHighlights,
  3410             "renderShadows": renderShadows,
  3411             "renderReflections": renderReflections,
  3412             "rayDepth": rayDepth
  3414     );
  3416     raytracer.renderScene(scene, null, 0);

mercurial