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