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