Roll leveldb 3f7758:803d69 (v1.17 -> v1.18)
[chromium-blink-merge.git] / chrome / test / data / dromaeo / tests / v8-raytrace.html
blob55c18257e597e602dc79baeac9db5d6c578cd095
1 <html>
2 <head>
3 <script src="../htmlrunner.js"></script>
4 <script>
5 // The ray tracer code in this file is written by Adam Burmister. It
6 // is available in its original form from:
7 //
8 // http://labs.flog.nz.co/raytracer/
9 //
10 // It has been modified slightly by Google to work as a standalone
11 // benchmark, but the all the computational code remains
12 // untouched. This file also contains a copy of the Prototype
13 // JavaScript framework which is used by the ray tracer.
16 var checkNumber;
18 // Create dummy objects if we're not running in a browser.
19 if (typeof document == 'undefined') {
20 document = { };
21 window = { opera: null };
22 navigator = { userAgent: null, appVersion: "" };
26 // ------------------------------------------------------------------------
27 // ------------------------------------------------------------------------
30 /* Prototype JavaScript framework, version 1.5.0
31 * (c) 2005-2007 Sam Stephenson
33 * Prototype is freely distributable under the terms of an MIT-style license.
34 * For details, see the Prototype web site: http://prototype.conio.net/
36 /*--------------------------------------------------------------------------*/
38 //--------------------
39 var Prototype = {
40 Version: '1.5.0',
41 BrowserFeatures: {
42 XPath: !!document.evaluate
45 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
46 emptyFunction: function() {},
47 K: function(x) { return x }
50 var Class = {
51 create: function() {
52 return function() {
53 this.initialize.apply(this, arguments);
58 var Abstract = new Object();
60 Object.extend = function(destination, source) {
61 for (var property in source) {
62 destination[property] = source[property];
64 return destination;
67 Object.extend(Object, {
68 inspect: function(object) {
69 try {
70 if (object === undefined) return 'undefined';
71 if (object === null) return 'null';
72 return object.inspect ? object.inspect() : object.toString();
73 } catch (e) {
74 if (e instanceof RangeError) return '...';
75 throw e;
79 keys: function(object) {
80 var keys = [];
81 for (var property in object)
82 keys.push(property);
83 return keys;
86 values: function(object) {
87 var values = [];
88 for (var property in object)
89 values.push(object[property]);
90 return values;
93 clone: function(object) {
94 return Object.extend({}, object);
96 });
98 Function.prototype.bind = function() {
99 var __method = this, args = $A(arguments), object = args.shift();
100 return function() {
101 return __method.apply(object, args.concat($A(arguments)));
105 Function.prototype.bindAsEventListener = function(object) {
106 var __method = this, args = $A(arguments), object = args.shift();
107 return function(event) {
108 return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
112 Object.extend(Number.prototype, {
113 toColorPart: function() {
114 var digits = this.toString(16);
115 if (this < 16) return '0' + digits;
116 return digits;
119 succ: function() {
120 return this + 1;
123 times: function(iterator) {
124 $R(0, this, true).each(iterator);
125 return this;
129 var Try = {
130 these: function() {
131 var returnValue;
133 for (var i = 0, length = arguments.length; i < length; i++) {
134 var lambda = arguments[i];
135 try {
136 returnValue = lambda();
137 break;
138 } catch (e) {}
141 return returnValue;
145 /*--------------------------------------------------------------------------*/
147 var PeriodicalExecuter = Class.create();
148 PeriodicalExecuter.prototype = {
149 initialize: function(callback, frequency) {
150 this.callback = callback;
151 this.frequency = frequency;
152 this.currentlyExecuting = false;
154 this.registerCallback();
157 registerCallback: function() {
158 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
161 stop: function() {
162 if (!this.timer) return;
163 clearInterval(this.timer);
164 this.timer = null;
167 onTimerEvent: function() {
168 if (!this.currentlyExecuting) {
169 try {
170 this.currentlyExecuting = true;
171 this.callback(this);
172 } finally {
173 this.currentlyExecuting = false;
178 String.interpret = function(value){
179 return value == null ? '' : String(value);
182 Object.extend(String.prototype, {
183 gsub: function(pattern, replacement) {
184 var result = '', source = this, match;
185 replacement = arguments.callee.prepareReplacement(replacement);
187 while (source.length > 0) {
188 if (match = source.match(pattern)) {
189 result += source.slice(0, match.index);
190 result += String.interpret(replacement(match));
191 source = source.slice(match.index + match[0].length);
192 } else {
193 result += source, source = '';
196 return result;
199 sub: function(pattern, replacement, count) {
200 replacement = this.gsub.prepareReplacement(replacement);
201 count = count === undefined ? 1 : count;
203 return this.gsub(pattern, function(match) {
204 if (--count < 0) return match[0];
205 return replacement(match);
209 scan: function(pattern, iterator) {
210 this.gsub(pattern, iterator);
211 return this;
214 truncate: function(length, truncation) {
215 length = length || 30;
216 truncation = truncation === undefined ? '...' : truncation;
217 return this.length > length ?
218 this.slice(0, length - truncation.length) + truncation : this;
221 strip: function() {
222 return this.replace(/^\s+/, '').replace(/\s+$/, '');
225 stripTags: function() {
226 return this.replace(/<\/?[^>]+>/gi, '');
229 stripScripts: function() {
230 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
233 extractScripts: function() {
234 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
235 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
236 return (this.match(matchAll) || []).map(function(scriptTag) {
237 return (scriptTag.match(matchOne) || ['', ''])[1];
241 evalScripts: function() {
242 return this.extractScripts().map(function(script) { return eval(script) });
245 escapeHTML: function() {
246 var div = document.createElement('div');
247 var text = document.createTextNode(this);
248 div.appendChild(text);
249 return div.innerHTML;
252 unescapeHTML: function() {
253 var div = document.createElement('div');
254 div.innerHTML = this.stripTags();
255 return div.childNodes[0] ? (div.childNodes.length > 1 ?
256 $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
257 div.childNodes[0].nodeValue) : '';
260 toQueryParams: function(separator) {
261 var match = this.strip().match(/([^?#]*)(#.*)?$/);
262 if (!match) return {};
264 return match[1].split(separator || '&').inject({}, function(hash, pair) {
265 if ((pair = pair.split('='))[0]) {
266 var name = decodeURIComponent(pair[0]);
267 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
269 if (hash[name] !== undefined) {
270 if (hash[name].constructor != Array)
271 hash[name] = [hash[name]];
272 if (value) hash[name].push(value);
274 else hash[name] = value;
276 return hash;
280 toArray: function() {
281 return this.split('');
284 succ: function() {
285 return this.slice(0, this.length - 1) +
286 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
289 camelize: function() {
290 var parts = this.split('-'), len = parts.length;
291 if (len == 1) return parts[0];
293 var camelized = this.charAt(0) == '-'
294 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
295 : parts[0];
297 for (var i = 1; i < len; i++)
298 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
300 return camelized;
303 capitalize: function(){
304 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
307 underscore: function() {
308 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
311 dasherize: function() {
312 return this.gsub(/_/,'-');
315 inspect: function(useDoubleQuotes) {
316 var escapedString = this.replace(/\\/g, '\\\\');
317 if (useDoubleQuotes)
318 return '"' + escapedString.replace(/"/g, '\\"') + '"';
319 else
320 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
324 String.prototype.gsub.prepareReplacement = function(replacement) {
325 if (typeof replacement == 'function') return replacement;
326 var template = new Template(replacement);
327 return function(match) { return template.evaluate(match) };
330 String.prototype.parseQuery = String.prototype.toQueryParams;
332 var Template = Class.create();
333 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
334 Template.prototype = {
335 initialize: function(template, pattern) {
336 this.template = template.toString();
337 this.pattern = pattern || Template.Pattern;
340 evaluate: function(object) {
341 return this.template.gsub(this.pattern, function(match) {
342 var before = match[1];
343 if (before == '\\') return match[2];
344 return before + String.interpret(object[match[3]]);
349 var $break = new Object();
350 var $continue = new Object();
352 var Enumerable = {
353 each: function(iterator) {
354 var index = 0;
355 try {
356 this._each(function(value) {
357 try {
358 iterator(value, index++);
359 } catch (e) {
360 if (e != $continue) throw e;
363 } catch (e) {
364 if (e != $break) throw e;
366 return this;
369 eachSlice: function(number, iterator) {
370 var index = -number, slices = [], array = this.toArray();
371 while ((index += number) < array.length)
372 slices.push(array.slice(index, index+number));
373 return slices.map(iterator);
376 all: function(iterator) {
377 var result = true;
378 this.each(function(value, index) {
379 result = result && !!(iterator || Prototype.K)(value, index);
380 if (!result) throw $break;
382 return result;
385 any: function(iterator) {
386 var result = false;
387 this.each(function(value, index) {
388 if (result = !!(iterator || Prototype.K)(value, index))
389 throw $break;
391 return result;
394 collect: function(iterator) {
395 var results = [];
396 this.each(function(value, index) {
397 results.push((iterator || Prototype.K)(value, index));
399 return results;
402 detect: function(iterator) {
403 var result;
404 this.each(function(value, index) {
405 if (iterator(value, index)) {
406 result = value;
407 throw $break;
410 return result;
413 findAll: function(iterator) {
414 var results = [];
415 this.each(function(value, index) {
416 if (iterator(value, index))
417 results.push(value);
419 return results;
422 grep: function(pattern, iterator) {
423 var results = [];
424 this.each(function(value, index) {
425 var stringValue = value.toString();
426 if (stringValue.match(pattern))
427 results.push((iterator || Prototype.K)(value, index));
429 return results;
432 include: function(object) {
433 var found = false;
434 this.each(function(value) {
435 if (value == object) {
436 found = true;
437 throw $break;
440 return found;
443 inGroupsOf: function(number, fillWith) {
444 fillWith = fillWith === undefined ? null : fillWith;
445 return this.eachSlice(number, function(slice) {
446 while(slice.length < number) slice.push(fillWith);
447 return slice;
451 inject: function(memo, iterator) {
452 this.each(function(value, index) {
453 memo = iterator(memo, value, index);
455 return memo;
458 invoke: function(method) {
459 var args = $A(arguments).slice(1);
460 return this.map(function(value) {
461 return value[method].apply(value, args);
465 max: function(iterator) {
466 var result;
467 this.each(function(value, index) {
468 value = (iterator || Prototype.K)(value, index);
469 if (result == undefined || value >= result)
470 result = value;
472 return result;
475 min: function(iterator) {
476 var result;
477 this.each(function(value, index) {
478 value = (iterator || Prototype.K)(value, index);
479 if (result == undefined || value < result)
480 result = value;
482 return result;
485 partition: function(iterator) {
486 var trues = [], falses = [];
487 this.each(function(value, index) {
488 ((iterator || Prototype.K)(value, index) ?
489 trues : falses).push(value);
491 return [trues, falses];
494 pluck: function(property) {
495 var results = [];
496 this.each(function(value, index) {
497 results.push(value[property]);
499 return results;
502 reject: function(iterator) {
503 var results = [];
504 this.each(function(value, index) {
505 if (!iterator(value, index))
506 results.push(value);
508 return results;
511 sortBy: function(iterator) {
512 return this.map(function(value, index) {
513 return {value: value, criteria: iterator(value, index)};
514 }).sort(function(left, right) {
515 var a = left.criteria, b = right.criteria;
516 return a < b ? -1 : a > b ? 1 : 0;
517 }).pluck('value');
520 toArray: function() {
521 return this.map();
524 zip: function() {
525 var iterator = Prototype.K, args = $A(arguments);
526 if (typeof args.last() == 'function')
527 iterator = args.pop();
529 var collections = [this].concat(args).map($A);
530 return this.map(function(value, index) {
531 return iterator(collections.pluck(index));
535 size: function() {
536 return this.toArray().length;
539 inspect: function() {
540 return '#<Enumerable:' + this.toArray().inspect() + '>';
544 Object.extend(Enumerable, {
545 map: Enumerable.collect,
546 find: Enumerable.detect,
547 select: Enumerable.findAll,
548 member: Enumerable.include,
549 entries: Enumerable.toArray
551 var $A = Array.from = function(iterable) {
552 if (!iterable) return [];
553 if (iterable.toArray) {
554 return iterable.toArray();
555 } else {
556 var results = [];
557 for (var i = 0, length = iterable.length; i < length; i++)
558 results.push(iterable[i]);
559 return results;
563 Object.extend(Array.prototype, Enumerable);
565 if (!Array.prototype._reverse)
566 Array.prototype._reverse = Array.prototype.reverse;
568 Object.extend(Array.prototype, {
569 _each: function(iterator) {
570 for (var i = 0, length = this.length; i < length; i++)
571 iterator(this[i]);
574 clear: function() {
575 this.length = 0;
576 return this;
579 first: function() {
580 return this[0];
583 last: function() {
584 return this[this.length - 1];
587 compact: function() {
588 return this.select(function(value) {
589 return value != null;
593 flatten: function() {
594 return this.inject([], function(array, value) {
595 return array.concat(value && value.constructor == Array ?
596 value.flatten() : [value]);
600 without: function() {
601 var values = $A(arguments);
602 return this.select(function(value) {
603 return !values.include(value);
607 indexOf: function(object) {
608 for (var i = 0, length = this.length; i < length; i++)
609 if (this[i] == object) return i;
610 return -1;
613 reverse: function(inline) {
614 return (inline !== false ? this : this.toArray())._reverse();
617 reduce: function() {
618 return this.length > 1 ? this : this[0];
621 uniq: function() {
622 return this.inject([], function(array, value) {
623 return array.include(value) ? array : array.concat([value]);
627 clone: function() {
628 return [].concat(this);
631 size: function() {
632 return this.length;
635 inspect: function() {
636 return '[' + this.map(Object.inspect).join(', ') + ']';
640 Array.prototype.toArray = Array.prototype.clone;
642 function $w(string){
643 string = string.strip();
644 return string ? string.split(/\s+/) : [];
647 if(window.opera){
648 Array.prototype.concat = function(){
649 var array = [];
650 for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
651 for(var i = 0, length = arguments.length; i < length; i++) {
652 if(arguments[i].constructor == Array) {
653 for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
654 array.push(arguments[i][j]);
655 } else {
656 array.push(arguments[i]);
659 return array;
662 var Hash = function(obj) {
663 Object.extend(this, obj || {});
666 Object.extend(Hash, {
667 toQueryString: function(obj) {
668 var parts = [];
670 this.prototype._each.call(obj, function(pair) {
671 if (!pair.key) return;
673 if (pair.value && pair.value.constructor == Array) {
674 var values = pair.value.compact();
675 if (values.length < 2) pair.value = values.reduce();
676 else {
677 key = encodeURIComponent(pair.key);
678 values.each(function(value) {
679 value = value != undefined ? encodeURIComponent(value) : '';
680 parts.push(key + '=' + encodeURIComponent(value));
682 return;
685 if (pair.value == undefined) pair[1] = '';
686 parts.push(pair.map(encodeURIComponent).join('='));
689 return parts.join('&');
693 Object.extend(Hash.prototype, Enumerable);
694 Object.extend(Hash.prototype, {
695 _each: function(iterator) {
696 for (var key in this) {
697 var value = this[key];
698 if (value && value == Hash.prototype[key]) continue;
700 var pair = [key, value];
701 pair.key = key;
702 pair.value = value;
703 iterator(pair);
707 keys: function() {
708 return this.pluck('key');
711 values: function() {
712 return this.pluck('value');
715 merge: function(hash) {
716 return $H(hash).inject(this, function(mergedHash, pair) {
717 mergedHash[pair.key] = pair.value;
718 return mergedHash;
722 remove: function() {
723 var result;
724 for(var i = 0, length = arguments.length; i < length; i++) {
725 var value = this[arguments[i]];
726 if (value !== undefined){
727 if (result === undefined) result = value;
728 else {
729 if (result.constructor != Array) result = [result];
730 result.push(value)
733 delete this[arguments[i]];
735 return result;
738 toQueryString: function() {
739 return Hash.toQueryString(this);
742 inspect: function() {
743 return '#<Hash:{' + this.map(function(pair) {
744 return pair.map(Object.inspect).join(': ');
745 }).join(', ') + '}>';
749 function $H(object) {
750 if (object && object.constructor == Hash) return object;
751 return new Hash(object);
753 ObjectRange = Class.create();
754 Object.extend(ObjectRange.prototype, Enumerable);
755 Object.extend(ObjectRange.prototype, {
756 initialize: function(start, end, exclusive) {
757 this.start = start;
758 this.end = end;
759 this.exclusive = exclusive;
762 _each: function(iterator) {
763 var value = this.start;
764 while (this.include(value)) {
765 iterator(value);
766 value = value.succ();
770 include: function(value) {
771 if (value < this.start)
772 return false;
773 if (this.exclusive)
774 return value < this.end;
775 return value <= this.end;
779 var $R = function(start, end, exclusive) {
780 return new ObjectRange(start, end, exclusive);
783 var Ajax = {
784 getTransport: function() {
785 return Try.these(
786 function() {return new XMLHttpRequest()},
787 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
788 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
789 ) || false;
792 activeRequestCount: 0
795 Ajax.Responders = {
796 responders: [],
798 _each: function(iterator) {
799 this.responders._each(iterator);
802 register: function(responder) {
803 if (!this.include(responder))
804 this.responders.push(responder);
807 unregister: function(responder) {
808 this.responders = this.responders.without(responder);
811 dispatch: function(callback, request, transport, json) {
812 this.each(function(responder) {
813 if (typeof responder[callback] == 'function') {
814 try {
815 responder[callback].apply(responder, [request, transport, json]);
816 } catch (e) {}
822 Object.extend(Ajax.Responders, Enumerable);
824 Ajax.Responders.register({
825 onCreate: function() {
826 Ajax.activeRequestCount++;
828 onComplete: function() {
829 Ajax.activeRequestCount--;
833 Ajax.Base = function() {};
834 Ajax.Base.prototype = {
835 setOptions: function(options) {
836 this.options = {
837 method: 'post',
838 asynchronous: true,
839 contentType: 'application/x-www-form-urlencoded',
840 encoding: 'UTF-8',
841 parameters: ''
843 Object.extend(this.options, options || {});
845 this.options.method = this.options.method.toLowerCase();
846 if (typeof this.options.parameters == 'string')
847 this.options.parameters = this.options.parameters.toQueryParams();
851 Ajax.Request = Class.create();
852 Ajax.Request.Events =
853 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
855 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
856 _complete: false,
858 initialize: function(url, options) {
859 this.transport = Ajax.getTransport();
860 this.setOptions(options);
861 this.request(url);
864 request: function(url) {
865 this.url = url;
866 this.method = this.options.method;
867 var params = this.options.parameters;
869 if (!['get', 'post'].include(this.method)) {
870 // simulate other verbs over post
871 params['_method'] = this.method;
872 this.method = 'post';
875 params = Hash.toQueryString(params);
876 if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
878 // when GET, append parameters to URL
879 if (this.method == 'get' && params)
880 this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
882 try {
883 Ajax.Responders.dispatch('onCreate', this, this.transport);
885 this.transport.open(this.method.toUpperCase(), this.url,
886 this.options.asynchronous);
888 if (this.options.asynchronous)
889 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
891 this.transport.onreadystatechange = this.onStateChange.bind(this);
892 this.setRequestHeaders();
894 var body = this.method == 'post' ? (this.options.postBody || params) : null;
896 this.transport.send(body);
898 /* Force Firefox to handle ready state 4 for synchronous requests */
899 if (!this.options.asynchronous && this.transport.overrideMimeType)
900 this.onStateChange();
903 catch (e) {
904 this.dispatchException(e);
908 onStateChange: function() {
909 var readyState = this.transport.readyState;
910 if (readyState > 1 && !((readyState == 4) && this._complete))
911 this.respondToReadyState(this.transport.readyState);
914 setRequestHeaders: function() {
915 var headers = {
916 'X-Requested-With': 'XMLHttpRequest',
917 'X-Prototype-Version': Prototype.Version,
918 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
921 if (this.method == 'post') {
922 headers['Content-type'] = this.options.contentType +
923 (this.options.encoding ? '; charset=' + this.options.encoding : '');
925 /* Force "Connection: close" for older Mozilla browsers to work
926 * around a bug where XMLHttpRequest sends an incorrect
927 * Content-length header. See Mozilla Bugzilla #246651.
929 if (this.transport.overrideMimeType &&
930 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
931 headers['Connection'] = 'close';
934 // user-defined headers
935 if (typeof this.options.requestHeaders == 'object') {
936 var extras = this.options.requestHeaders;
938 if (typeof extras.push == 'function')
939 for (var i = 0, length = extras.length; i < length; i += 2)
940 headers[extras[i]] = extras[i+1];
941 else
942 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
945 for (var name in headers)
946 this.transport.setRequestHeader(name, headers[name]);
949 success: function() {
950 return !this.transport.status
951 || (this.transport.status >= 200 && this.transport.status < 300);
954 respondToReadyState: function(readyState) {
955 var state = Ajax.Request.Events[readyState];
956 var transport = this.transport, json = this.evalJSON();
958 if (state == 'Complete') {
959 try {
960 this._complete = true;
961 (this.options['on' + this.transport.status]
962 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
963 || Prototype.emptyFunction)(transport, json);
964 } catch (e) {
965 this.dispatchException(e);
968 if ((this.getHeader('Content-type') || 'text/javascript').strip().
969 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
970 this.evalResponse();
973 try {
974 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
975 Ajax.Responders.dispatch('on' + state, this, transport, json);
976 } catch (e) {
977 this.dispatchException(e);
980 if (state == 'Complete') {
981 // avoid memory leak in MSIE: clean up
982 this.transport.onreadystatechange = Prototype.emptyFunction;
986 getHeader: function(name) {
987 try {
988 return this.transport.getResponseHeader(name);
989 } catch (e) { return null }
992 evalJSON: function() {
993 try {
994 var json = this.getHeader('X-JSON');
995 return json ? eval('(' + json + ')') : null;
996 } catch (e) { return null }
999 evalResponse: function() {
1000 try {
1001 return eval(this.transport.responseText);
1002 } catch (e) {
1003 this.dispatchException(e);
1007 dispatchException: function(exception) {
1008 (this.options.onException || Prototype.emptyFunction)(this, exception);
1009 Ajax.Responders.dispatch('onException', this, exception);
1013 Ajax.Updater = Class.create();
1015 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1016 initialize: function(container, url, options) {
1017 this.container = {
1018 success: (container.success || container),
1019 failure: (container.failure || (container.success ? null : container))
1022 this.transport = Ajax.getTransport();
1023 this.setOptions(options);
1025 var onComplete = this.options.onComplete || Prototype.emptyFunction;
1026 this.options.onComplete = (function(transport, param) {
1027 this.updateContent();
1028 onComplete(transport, param);
1029 }).bind(this);
1031 this.request(url);
1034 updateContent: function() {
1035 var receiver = this.container[this.success() ? 'success' : 'failure'];
1036 var response = this.transport.responseText;
1038 if (!this.options.evalScripts) response = response.stripScripts();
1040 if (receiver = $(receiver)) {
1041 if (this.options.insertion)
1042 new this.options.insertion(receiver, response);
1043 else
1044 receiver.update(response);
1047 if (this.success()) {
1048 if (this.onComplete)
1049 setTimeout(this.onComplete.bind(this), 10);
1054 Ajax.PeriodicalUpdater = Class.create();
1055 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1056 initialize: function(container, url, options) {
1057 this.setOptions(options);
1058 this.onComplete = this.options.onComplete;
1060 this.frequency = (this.options.frequency || 2);
1061 this.decay = (this.options.decay || 1);
1063 this.updater = {};
1064 this.container = container;
1065 this.url = url;
1067 this.start();
1070 start: function() {
1071 this.options.onComplete = this.updateComplete.bind(this);
1072 this.onTimerEvent();
1075 stop: function() {
1076 this.updater.options.onComplete = undefined;
1077 clearTimeout(this.timer);
1078 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1081 updateComplete: function(request) {
1082 if (this.options.decay) {
1083 this.decay = (request.responseText == this.lastText ?
1084 this.decay * this.options.decay : 1);
1086 this.lastText = request.responseText;
1088 this.timer = setTimeout(this.onTimerEvent.bind(this),
1089 this.decay * this.frequency * 1000);
1092 onTimerEvent: function() {
1093 this.updater = new Ajax.Updater(this.container, this.url, this.options);
1096 function $(element) {
1097 if (arguments.length > 1) {
1098 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1099 elements.push($(arguments[i]));
1100 return elements;
1102 if (typeof element == 'string')
1103 element = document.getElementById(element);
1104 return Element.extend(element);
1107 if (Prototype.BrowserFeatures.XPath) {
1108 document._getElementsByXPath = function(expression, parentElement) {
1109 var results = [];
1110 var query = document.evaluate(expression, $(parentElement) || document,
1111 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1112 for (var i = 0, length = query.snapshotLength; i < length; i++)
1113 results.push(query.snapshotItem(i));
1114 return results;
1118 document.getElementsByClassName = function(className, parentElement) {
1119 if (Prototype.BrowserFeatures.XPath) {
1120 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1121 return document._getElementsByXPath(q, parentElement);
1122 } else {
1123 var children = ($(parentElement) || document.body).getElementsByTagName('*');
1124 var elements = [], child;
1125 for (var i = 0, length = children.length; i < length; i++) {
1126 child = children[i];
1127 if (Element.hasClassName(child, className))
1128 elements.push(Element.extend(child));
1130 return elements;
1134 /*--------------------------------------------------------------------------*/
1136 if (!window.Element)
1137 var Element = new Object();
1139 Element.extend = function(element) {
1140 if (!element || _nativeExtensions || element.nodeType == 3) return element;
1142 if (!element._extended && element.tagName && element != window) {
1143 var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
1145 if (element.tagName == 'FORM')
1146 Object.extend(methods, Form.Methods);
1147 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
1148 Object.extend(methods, Form.Element.Methods);
1150 Object.extend(methods, Element.Methods.Simulated);
1152 for (var property in methods) {
1153 var value = methods[property];
1154 if (typeof value == 'function' && !(property in element))
1155 element[property] = cache.findOrStore(value);
1159 element._extended = true;
1160 return element;
1163 Element.extend.cache = {
1164 findOrStore: function(value) {
1165 return this[value] = this[value] || function() {
1166 return value.apply(null, [this].concat($A(arguments)));
1171 Element.Methods = {
1172 visible: function(element) {
1173 return $(element).style.display != 'none';
1176 toggle: function(element) {
1177 element = $(element);
1178 Element[Element.visible(element) ? 'hide' : 'show'](element);
1179 return element;
1182 hide: function(element) {
1183 $(element).style.display = 'none';
1184 return element;
1187 show: function(element) {
1188 $(element).style.display = '';
1189 return element;
1192 remove: function(element) {
1193 element = $(element);
1194 element.parentNode.removeChild(element);
1195 return element;
1198 update: function(element, html) {
1199 html = typeof html == 'undefined' ? '' : html.toString();
1200 $(element).innerHTML = html.stripScripts();
1201 setTimeout(function() {html.evalScripts()}, 10);
1202 return element;
1205 replace: function(element, html) {
1206 element = $(element);
1207 html = typeof html == 'undefined' ? '' : html.toString();
1208 if (element.outerHTML) {
1209 element.outerHTML = html.stripScripts();
1210 } else {
1211 var range = element.ownerDocument.createRange();
1212 range.selectNodeContents(element);
1213 element.parentNode.replaceChild(
1214 range.createContextualFragment(html.stripScripts()), element);
1216 setTimeout(function() {html.evalScripts()}, 10);
1217 return element;
1220 inspect: function(element) {
1221 element = $(element);
1222 var result = '<' + element.tagName.toLowerCase();
1223 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1224 var property = pair.first(), attribute = pair.last();
1225 var value = (element[property] || '').toString();
1226 if (value) result += ' ' + attribute + '=' + value.inspect(true);
1228 return result + '>';
1231 recursivelyCollect: function(element, property) {
1232 element = $(element);
1233 var elements = [];
1234 while (element = element[property])
1235 if (element.nodeType == 1)
1236 elements.push(Element.extend(element));
1237 return elements;
1240 ancestors: function(element) {
1241 return $(element).recursivelyCollect('parentNode');
1244 descendants: function(element) {
1245 return $A($(element).getElementsByTagName('*'));
1248 immediateDescendants: function(element) {
1249 if (!(element = $(element).firstChild)) return [];
1250 while (element && element.nodeType != 1) element = element.nextSibling;
1251 if (element) return [element].concat($(element).nextSiblings());
1252 return [];
1255 previousSiblings: function(element) {
1256 return $(element).recursivelyCollect('previousSibling');
1259 nextSiblings: function(element) {
1260 return $(element).recursivelyCollect('nextSibling');
1263 siblings: function(element) {
1264 element = $(element);
1265 return element.previousSiblings().reverse().concat(element.nextSiblings());
1268 match: function(element, selector) {
1269 if (typeof selector == 'string')
1270 selector = new Selector(selector);
1271 return selector.match($(element));
1274 up: function(element, expression, index) {
1275 return Selector.findElement($(element).ancestors(), expression, index);
1278 down: function(element, expression, index) {
1279 return Selector.findElement($(element).descendants(), expression, index);
1282 previous: function(element, expression, index) {
1283 return Selector.findElement($(element).previousSiblings(), expression, index);
1286 next: function(element, expression, index) {
1287 return Selector.findElement($(element).nextSiblings(), expression, index);
1290 getElementsBySelector: function() {
1291 var args = $A(arguments), element = $(args.shift());
1292 return Selector.findChildElements(element, args);
1295 getElementsByClassName: function(element, className) {
1296 return document.getElementsByClassName(className, element);
1299 readAttribute: function(element, name) {
1300 element = $(element);
1301 if (document.all && !window.opera) {
1302 var t = Element._attributeTranslations;
1303 if (t.values[name]) return t.values[name](element, name);
1304 if (t.names[name]) name = t.names[name];
1305 var attribute = element.attributes[name];
1306 if(attribute) return attribute.nodeValue;
1308 return element.getAttribute(name);
1311 getHeight: function(element) {
1312 return $(element).getDimensions().height;
1315 getWidth: function(element) {
1316 return $(element).getDimensions().width;
1319 classNames: function(element) {
1320 return new Element.ClassNames(element);
1323 hasClassName: function(element, className) {
1324 if (!(element = $(element))) return;
1325 var elementClassName = element.className;
1326 if (elementClassName.length == 0) return false;
1327 if (elementClassName == className ||
1328 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1329 return true;
1330 return false;
1333 addClassName: function(element, className) {
1334 if (!(element = $(element))) return;
1335 Element.classNames(element).add(className);
1336 return element;
1339 removeClassName: function(element, className) {
1340 if (!(element = $(element))) return;
1341 Element.classNames(element).remove(className);
1342 return element;
1345 toggleClassName: function(element, className) {
1346 if (!(element = $(element))) return;
1347 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1348 return element;
1351 observe: function() {
1352 Event.observe.apply(Event, arguments);
1353 return $A(arguments).first();
1356 stopObserving: function() {
1357 Event.stopObserving.apply(Event, arguments);
1358 return $A(arguments).first();
1361 // removes whitespace-only text node children
1362 cleanWhitespace: function(element) {
1363 element = $(element);
1364 var node = element.firstChild;
1365 while (node) {
1366 var nextNode = node.nextSibling;
1367 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1368 element.removeChild(node);
1369 node = nextNode;
1371 return element;
1374 empty: function(element) {
1375 return $(element).innerHTML.match(/^\s*$/);
1378 descendantOf: function(element, ancestor) {
1379 element = $(element), ancestor = $(ancestor);
1380 while (element = element.parentNode)
1381 if (element == ancestor) return true;
1382 return false;
1385 scrollTo: function(element) {
1386 element = $(element);
1387 var pos = Position.cumulativeOffset(element);
1388 window.scrollTo(pos[0], pos[1]);
1389 return element;
1392 getStyle: function(element, style) {
1393 element = $(element);
1394 if (['float','cssFloat'].include(style))
1395 style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
1396 style = style.camelize();
1397 var value = element.style[style];
1398 if (!value) {
1399 if (document.defaultView && document.defaultView.getComputedStyle) {
1400 var css = document.defaultView.getComputedStyle(element, null);
1401 value = css ? css[style] : null;
1402 } else if (element.currentStyle) {
1403 value = element.currentStyle[style];
1407 if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
1408 value = element['offset'+style.capitalize()] + 'px';
1410 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
1411 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
1412 if(style == 'opacity') {
1413 if(value) return parseFloat(value);
1414 if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1415 if(value[1]) return parseFloat(value[1]) / 100;
1416 return 1.0;
1418 return value == 'auto' ? null : value;
1421 setStyle: function(element, style) {
1422 element = $(element);
1423 for (var name in style) {
1424 var value = style[name];
1425 if(name == 'opacity') {
1426 if (value == 1) {
1427 value = (/Gecko/.test(navigator.userAgent) &&
1428 !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
1429 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1430 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1431 } else if(value == '') {
1432 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1433 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1434 } else {
1435 if(value < 0.00001) value = 0;
1436 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1437 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
1438 'alpha(opacity='+value*100+')';
1440 } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
1441 element.style[name.camelize()] = value;
1443 return element;
1446 getDimensions: function(element) {
1447 element = $(element);
1448 var display = $(element).getStyle('display');
1449 if (display != 'none' && display != null) // Safari bug
1450 return {width: element.offsetWidth, height: element.offsetHeight};
1452 // All *Width and *Height properties give 0 on elements with display none,
1453 // so enable the element temporarily
1454 var els = element.style;
1455 var originalVisibility = els.visibility;
1456 var originalPosition = els.position;
1457 var originalDisplay = els.display;
1458 els.visibility = 'hidden';
1459 els.position = 'absolute';
1460 els.display = 'block';
1461 var originalWidth = element.clientWidth;
1462 var originalHeight = element.clientHeight;
1463 els.display = originalDisplay;
1464 els.position = originalPosition;
1465 els.visibility = originalVisibility;
1466 return {width: originalWidth, height: originalHeight};
1469 makePositioned: function(element) {
1470 element = $(element);
1471 var pos = Element.getStyle(element, 'position');
1472 if (pos == 'static' || !pos) {
1473 element._madePositioned = true;
1474 element.style.position = 'relative';
1475 // Opera returns the offset relative to the positioning context, when an
1476 // element is position relative but top and left have not been defined
1477 if (window.opera) {
1478 element.style.top = 0;
1479 element.style.left = 0;
1482 return element;
1485 undoPositioned: function(element) {
1486 element = $(element);
1487 if (element._madePositioned) {
1488 element._madePositioned = undefined;
1489 element.style.position =
1490 element.style.top =
1491 element.style.left =
1492 element.style.bottom =
1493 element.style.right = '';
1495 return element;
1498 makeClipping: function(element) {
1499 element = $(element);
1500 if (element._overflow) return element;
1501 element._overflow = element.style.overflow || 'auto';
1502 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1503 element.style.overflow = 'hidden';
1504 return element;
1507 undoClipping: function(element) {
1508 element = $(element);
1509 if (!element._overflow) return element;
1510 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1511 element._overflow = null;
1512 return element;
1516 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
1518 Element._attributeTranslations = {};
1520 Element._attributeTranslations.names = {
1521 colspan: "colSpan",
1522 rowspan: "rowSpan",
1523 valign: "vAlign",
1524 datetime: "dateTime",
1525 accesskey: "accessKey",
1526 tabindex: "tabIndex",
1527 enctype: "encType",
1528 maxlength: "maxLength",
1529 readonly: "readOnly",
1530 longdesc: "longDesc"
1533 Element._attributeTranslations.values = {
1534 _getAttr: function(element, attribute) {
1535 return element.getAttribute(attribute, 2);
1538 _flag: function(element, attribute) {
1539 return $(element).hasAttribute(attribute) ? attribute : null;
1542 style: function(element) {
1543 return element.style.cssText.toLowerCase();
1546 title: function(element) {
1547 var node = element.getAttributeNode('title');
1548 return node.specified ? node.nodeValue : null;
1552 Object.extend(Element._attributeTranslations.values, {
1553 href: Element._attributeTranslations.values._getAttr,
1554 src: Element._attributeTranslations.values._getAttr,
1555 disabled: Element._attributeTranslations.values._flag,
1556 checked: Element._attributeTranslations.values._flag,
1557 readonly: Element._attributeTranslations.values._flag,
1558 multiple: Element._attributeTranslations.values._flag
1561 Element.Methods.Simulated = {
1562 hasAttribute: function(element, attribute) {
1563 var t = Element._attributeTranslations;
1564 attribute = t.names[attribute] || attribute;
1565 return $(element).getAttributeNode(attribute).specified;
1569 // IE is missing .innerHTML support for TABLE-related elements
1570 if (document.all && !window.opera){
1571 Element.Methods.update = function(element, html) {
1572 element = $(element);
1573 html = typeof html == 'undefined' ? '' : html.toString();
1574 var tagName = element.tagName.toUpperCase();
1575 if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1576 var div = document.createElement('div');
1577 switch (tagName) {
1578 case 'THEAD':
1579 case 'TBODY':
1580 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1581 depth = 2;
1582 break;
1583 case 'TR':
1584 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1585 depth = 3;
1586 break;
1587 case 'TD':
1588 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1589 depth = 4;
1591 $A(element.childNodes).each(function(node){
1592 element.removeChild(node)
1594 depth.times(function(){ div = div.firstChild });
1596 $A(div.childNodes).each(
1597 function(node){ element.appendChild(node) });
1598 } else {
1599 element.innerHTML = html.stripScripts();
1601 setTimeout(function() {html.evalScripts()}, 10);
1602 return element;
1606 Object.extend(Element, Element.Methods);
1608 var _nativeExtensions = false;
1610 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1611 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
1612 var className = 'HTML' + tag + 'Element';
1613 if(window[className]) return;
1614 var klass = window[className] = {};
1615 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
1618 Element.addMethods = function(methods) {
1619 Object.extend(Element.Methods, methods || {});
1621 function copy(methods, destination, onlyIfAbsent) {
1622 onlyIfAbsent = onlyIfAbsent || false;
1623 var cache = Element.extend.cache;
1624 for (var property in methods) {
1625 var value = methods[property];
1626 if (!onlyIfAbsent || !(property in destination))
1627 destination[property] = cache.findOrStore(value);
1631 if (typeof HTMLElement != 'undefined') {
1632 copy(Element.Methods, HTMLElement.prototype);
1633 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1634 copy(Form.Methods, HTMLFormElement.prototype);
1635 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
1636 copy(Form.Element.Methods, klass.prototype);
1638 _nativeExtensions = true;
1642 var Toggle = new Object();
1643 Toggle.display = Element.toggle;
1645 /*--------------------------------------------------------------------------*/
1647 Abstract.Insertion = function(adjacency) {
1648 this.adjacency = adjacency;
1651 Abstract.Insertion.prototype = {
1652 initialize: function(element, content) {
1653 this.element = $(element);
1654 this.content = content.stripScripts();
1656 if (this.adjacency && this.element.insertAdjacentHTML) {
1657 try {
1658 this.element.insertAdjacentHTML(this.adjacency, this.content);
1659 } catch (e) {
1660 var tagName = this.element.tagName.toUpperCase();
1661 if (['TBODY', 'TR'].include(tagName)) {
1662 this.insertContent(this.contentFromAnonymousTable());
1663 } else {
1664 throw e;
1667 } else {
1668 this.range = this.element.ownerDocument.createRange();
1669 if (this.initializeRange) this.initializeRange();
1670 this.insertContent([this.range.createContextualFragment(this.content)]);
1673 setTimeout(function() {content.evalScripts()}, 10);
1676 contentFromAnonymousTable: function() {
1677 var div = document.createElement('div');
1678 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1679 return $A(div.childNodes[0].childNodes[0].childNodes);
1683 var Insertion = new Object();
1685 Insertion.Before = Class.create();
1686 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1687 initializeRange: function() {
1688 this.range.setStartBefore(this.element);
1691 insertContent: function(fragments) {
1692 fragments.each((function(fragment) {
1693 this.element.parentNode.insertBefore(fragment, this.element);
1694 }).bind(this));
1698 Insertion.Top = Class.create();
1699 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1700 initializeRange: function() {
1701 this.range.selectNodeContents(this.element);
1702 this.range.collapse(true);
1705 insertContent: function(fragments) {
1706 fragments.reverse(false).each((function(fragment) {
1707 this.element.insertBefore(fragment, this.element.firstChild);
1708 }).bind(this));
1712 Insertion.Bottom = Class.create();
1713 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1714 initializeRange: function() {
1715 this.range.selectNodeContents(this.element);
1716 this.range.collapse(this.element);
1719 insertContent: function(fragments) {
1720 fragments.each((function(fragment) {
1721 this.element.appendChild(fragment);
1722 }).bind(this));
1726 Insertion.After = Class.create();
1727 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1728 initializeRange: function() {
1729 this.range.setStartAfter(this.element);
1732 insertContent: function(fragments) {
1733 fragments.each((function(fragment) {
1734 this.element.parentNode.insertBefore(fragment,
1735 this.element.nextSibling);
1736 }).bind(this));
1740 /*--------------------------------------------------------------------------*/
1742 Element.ClassNames = Class.create();
1743 Element.ClassNames.prototype = {
1744 initialize: function(element) {
1745 this.element = $(element);
1748 _each: function(iterator) {
1749 this.element.className.split(/\s+/).select(function(name) {
1750 return name.length > 0;
1751 })._each(iterator);
1754 set: function(className) {
1755 this.element.className = className;
1758 add: function(classNameToAdd) {
1759 if (this.include(classNameToAdd)) return;
1760 this.set($A(this).concat(classNameToAdd).join(' '));
1763 remove: function(classNameToRemove) {
1764 if (!this.include(classNameToRemove)) return;
1765 this.set($A(this).without(classNameToRemove).join(' '));
1768 toString: function() {
1769 return $A(this).join(' ');
1773 Object.extend(Element.ClassNames.prototype, Enumerable);
1774 var Selector = Class.create();
1775 Selector.prototype = {
1776 initialize: function(expression) {
1777 this.params = {classNames: []};
1778 this.expression = expression.toString().strip();
1779 this.parseExpression();
1780 this.compileMatcher();
1783 parseExpression: function() {
1784 function abort(message) { throw 'Parse error in selector: ' + message; }
1786 if (this.expression == '') abort('empty expression');
1788 var params = this.params, expr = this.expression, match, modifier, clause, rest;
1789 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
1790 params.attributes = params.attributes || [];
1791 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
1792 expr = match[1];
1795 if (expr == '*') return this.params.wildcard = true;
1797 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
1798 modifier = match[1], clause = match[2], rest = match[3];
1799 switch (modifier) {
1800 case '#': params.id = clause; break;
1801 case '.': params.classNames.push(clause); break;
1802 case '':
1803 case undefined: params.tagName = clause.toUpperCase(); break;
1804 default: abort(expr.inspect());
1806 expr = rest;
1809 if (expr.length > 0) abort(expr.inspect());
1812 buildMatchExpression: function() {
1813 var params = this.params, conditions = [], clause;
1815 if (params.wildcard)
1816 conditions.push('true');
1817 if (clause = params.id)
1818 conditions.push('element.readAttribute("id") == ' + clause.inspect());
1819 if (clause = params.tagName)
1820 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
1821 if ((clause = params.classNames).length > 0)
1822 for (var i = 0, length = clause.length; i < length; i++)
1823 conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
1824 if (clause = params.attributes) {
1825 clause.each(function(attribute) {
1826 var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
1827 var splitValueBy = function(delimiter) {
1828 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
1831 switch (attribute.operator) {
1832 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
1833 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
1834 case '|=': conditions.push(
1835 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
1836 ); break;
1837 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
1838 case '':
1839 case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
1840 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
1845 return conditions.join(' && ');
1848 compileMatcher: function() {
1849 this.match = new Function('element', 'if (!element.tagName) return false; \
1850 element = $(element); \
1851 return ' + this.buildMatchExpression());
1854 findElements: function(scope) {
1855 var element;
1857 if (element = $(this.params.id))
1858 if (this.match(element))
1859 if (!scope || Element.childOf(element, scope))
1860 return [element];
1862 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
1864 var results = [];
1865 for (var i = 0, length = scope.length; i < length; i++)
1866 if (this.match(element = scope[i]))
1867 results.push(Element.extend(element));
1869 return results;
1872 toString: function() {
1873 return this.expression;
1877 Object.extend(Selector, {
1878 matchElements: function(elements, expression) {
1879 var selector = new Selector(expression);
1880 return elements.select(selector.match.bind(selector)).map(Element.extend);
1883 findElement: function(elements, expression, index) {
1884 if (typeof expression == 'number') index = expression, expression = false;
1885 return Selector.matchElements(elements, expression || '*')[index || 0];
1888 findChildElements: function(element, expressions) {
1889 return expressions.map(function(expression) {
1890 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
1891 var selector = new Selector(expr);
1892 return results.inject([], function(elements, result) {
1893 return elements.concat(selector.findElements(result || element));
1896 }).flatten();
1900 function $$() {
1901 return Selector.findChildElements(document, $A(arguments));
1903 var Form = {
1904 reset: function(form) {
1905 $(form).reset();
1906 return form;
1909 serializeElements: function(elements, getHash) {
1910 var data = elements.inject({}, function(result, element) {
1911 if (!element.disabled && element.name) {
1912 var key = element.name, value = $(element).getValue();
1913 if (value != undefined) {
1914 if (result[key]) {
1915 if (result[key].constructor != Array) result[key] = [result[key]];
1916 result[key].push(value);
1918 else result[key] = value;
1921 return result;
1924 return getHash ? data : Hash.toQueryString(data);
1928 Form.Methods = {
1929 serialize: function(form, getHash) {
1930 return Form.serializeElements(Form.getElements(form), getHash);
1933 getElements: function(form) {
1934 return $A($(form).getElementsByTagName('*')).inject([],
1935 function(elements, child) {
1936 if (Form.Element.Serializers[child.tagName.toLowerCase()])
1937 elements.push(Element.extend(child));
1938 return elements;
1943 getInputs: function(form, typeName, name) {
1944 form = $(form);
1945 var inputs = form.getElementsByTagName('input');
1947 if (!typeName && !name) return $A(inputs).map(Element.extend);
1949 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
1950 var input = inputs[i];
1951 if ((typeName && input.type != typeName) || (name && input.name != name))
1952 continue;
1953 matchingInputs.push(Element.extend(input));
1956 return matchingInputs;
1959 disable: function(form) {
1960 form = $(form);
1961 form.getElements().each(function(element) {
1962 element.blur();
1963 element.disabled = 'true';
1965 return form;
1968 enable: function(form) {
1969 form = $(form);
1970 form.getElements().each(function(element) {
1971 element.disabled = '';
1973 return form;
1976 findFirstElement: function(form) {
1977 return $(form).getElements().find(function(element) {
1978 return element.type != 'hidden' && !element.disabled &&
1979 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1983 focusFirstElement: function(form) {
1984 form = $(form);
1985 form.findFirstElement().activate();
1986 return form;
1990 Object.extend(Form, Form.Methods);
1992 /*--------------------------------------------------------------------------*/
1994 Form.Element = {
1995 focus: function(element) {
1996 $(element).focus();
1997 return element;
2000 select: function(element) {
2001 $(element).select();
2002 return element;
2006 Form.Element.Methods = {
2007 serialize: function(element) {
2008 element = $(element);
2009 if (!element.disabled && element.name) {
2010 var value = element.getValue();
2011 if (value != undefined) {
2012 var pair = {};
2013 pair[element.name] = value;
2014 return Hash.toQueryString(pair);
2017 return '';
2020 getValue: function(element) {
2021 element = $(element);
2022 var method = element.tagName.toLowerCase();
2023 return Form.Element.Serializers[method](element);
2026 clear: function(element) {
2027 $(element).value = '';
2028 return element;
2031 present: function(element) {
2032 return $(element).value != '';
2035 activate: function(element) {
2036 element = $(element);
2037 element.focus();
2038 if (element.select && ( element.tagName.toLowerCase() != 'input' ||
2039 !['button', 'reset', 'submit'].include(element.type) ) )
2040 element.select();
2041 return element;
2044 disable: function(element) {
2045 element = $(element);
2046 element.disabled = true;
2047 return element;
2050 enable: function(element) {
2051 element = $(element);
2052 element.blur();
2053 element.disabled = false;
2054 return element;
2058 Object.extend(Form.Element, Form.Element.Methods);
2059 var Field = Form.Element;
2060 var $F = Form.Element.getValue;
2062 /*--------------------------------------------------------------------------*/
2064 Form.Element.Serializers = {
2065 input: function(element) {
2066 switch (element.type.toLowerCase()) {
2067 case 'checkbox':
2068 case 'radio':
2069 return Form.Element.Serializers.inputSelector(element);
2070 default:
2071 return Form.Element.Serializers.textarea(element);
2075 inputSelector: function(element) {
2076 return element.checked ? element.value : null;
2079 textarea: function(element) {
2080 return element.value;
2083 select: function(element) {
2084 return this[element.type == 'select-one' ?
2085 'selectOne' : 'selectMany'](element);
2088 selectOne: function(element) {
2089 var index = element.selectedIndex;
2090 return index >= 0 ? this.optionValue(element.options[index]) : null;
2093 selectMany: function(element) {
2094 var values, length = element.length;
2095 if (!length) return null;
2097 for (var i = 0, values = []; i < length; i++) {
2098 var opt = element.options[i];
2099 if (opt.selected) values.push(this.optionValue(opt));
2101 return values;
2104 optionValue: function(opt) {
2105 // extend element because hasAttribute may not be native
2106 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2110 /*--------------------------------------------------------------------------*/
2112 Abstract.TimedObserver = function() {}
2113 Abstract.TimedObserver.prototype = {
2114 initialize: function(element, frequency, callback) {
2115 this.frequency = frequency;
2116 this.element = $(element);
2117 this.callback = callback;
2119 this.lastValue = this.getValue();
2120 this.registerCallback();
2123 registerCallback: function() {
2124 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
2127 onTimerEvent: function() {
2128 var value = this.getValue();
2129 var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2130 ? this.lastValue != value : String(this.lastValue) != String(value));
2131 if (changed) {
2132 this.callback(this.element, value);
2133 this.lastValue = value;
2138 Form.Element.Observer = Class.create();
2139 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2140 getValue: function() {
2141 return Form.Element.getValue(this.element);
2145 Form.Observer = Class.create();
2146 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2147 getValue: function() {
2148 return Form.serialize(this.element);
2152 /*--------------------------------------------------------------------------*/
2154 Abstract.EventObserver = function() {}
2155 Abstract.EventObserver.prototype = {
2156 initialize: function(element, callback) {
2157 this.element = $(element);
2158 this.callback = callback;
2160 this.lastValue = this.getValue();
2161 if (this.element.tagName.toLowerCase() == 'form')
2162 this.registerFormCallbacks();
2163 else
2164 this.registerCallback(this.element);
2167 onElementEvent: function() {
2168 var value = this.getValue();
2169 if (this.lastValue != value) {
2170 this.callback(this.element, value);
2171 this.lastValue = value;
2175 registerFormCallbacks: function() {
2176 Form.getElements(this.element).each(this.registerCallback.bind(this));
2179 registerCallback: function(element) {
2180 if (element.type) {
2181 switch (element.type.toLowerCase()) {
2182 case 'checkbox':
2183 case 'radio':
2184 Event.observe(element, 'click', this.onElementEvent.bind(this));
2185 break;
2186 default:
2187 Event.observe(element, 'change', this.onElementEvent.bind(this));
2188 break;
2194 Form.Element.EventObserver = Class.create();
2195 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2196 getValue: function() {
2197 return Form.Element.getValue(this.element);
2201 Form.EventObserver = Class.create();
2202 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2203 getValue: function() {
2204 return Form.serialize(this.element);
2207 if (!window.Event) {
2208 var Event = new Object();
2211 Object.extend(Event, {
2212 KEY_BACKSPACE: 8,
2213 KEY_TAB: 9,
2214 KEY_RETURN: 13,
2215 KEY_ESC: 27,
2216 KEY_LEFT: 37,
2217 KEY_UP: 38,
2218 KEY_RIGHT: 39,
2219 KEY_DOWN: 40,
2220 KEY_DELETE: 46,
2221 KEY_HOME: 36,
2222 KEY_END: 35,
2223 KEY_PAGEUP: 33,
2224 KEY_PAGEDOWN: 34,
2226 element: function(event) {
2227 return event.target || event.srcElement;
2230 isLeftClick: function(event) {
2231 return (((event.which) && (event.which == 1)) ||
2232 ((event.button) && (event.button == 1)));
2235 pointerX: function(event) {
2236 return event.pageX || (event.clientX +
2237 (document.documentElement.scrollLeft || document.body.scrollLeft));
2240 pointerY: function(event) {
2241 return event.pageY || (event.clientY +
2242 (document.documentElement.scrollTop || document.body.scrollTop));
2245 stop: function(event) {
2246 if (event.preventDefault) {
2247 event.preventDefault();
2248 event.stopPropagation();
2249 } else {
2250 event.returnValue = false;
2251 event.cancelBubble = true;
2255 // find the first node with the given tagName, starting from the
2256 // node the event was triggered on; traverses the DOM upwards
2257 findElement: function(event, tagName) {
2258 var element = Event.element(event);
2259 while (element.parentNode && (!element.tagName ||
2260 (element.tagName.toUpperCase() != tagName.toUpperCase())))
2261 element = element.parentNode;
2262 return element;
2265 observers: false,
2267 _observeAndCache: function(element, name, observer, useCapture) {
2268 if (!this.observers) this.observers = [];
2269 if (element.addEventListener) {
2270 this.observers.push([element, name, observer, useCapture]);
2271 element.addEventListener(name, observer, useCapture);
2272 } else if (element.attachEvent) {
2273 this.observers.push([element, name, observer, useCapture]);
2274 element.attachEvent('on' + name, observer);
2278 unloadCache: function() {
2279 if (!Event.observers) return;
2280 for (var i = 0, length = Event.observers.length; i < length; i++) {
2281 Event.stopObserving.apply(this, Event.observers[i]);
2282 Event.observers[i][0] = null;
2284 Event.observers = false;
2287 observe: function(element, name, observer, useCapture) {
2288 element = $(element);
2289 useCapture = useCapture || false;
2291 if (name == 'keypress' &&
2292 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2293 || element.attachEvent))
2294 name = 'keydown';
2296 Event._observeAndCache(element, name, observer, useCapture);
2299 stopObserving: function(element, name, observer, useCapture) {
2300 element = $(element);
2301 useCapture = useCapture || false;
2303 if (name == 'keypress' &&
2304 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2305 || element.detachEvent))
2306 name = 'keydown';
2308 if (element.removeEventListener) {
2309 element.removeEventListener(name, observer, useCapture);
2310 } else if (element.detachEvent) {
2311 try {
2312 element.detachEvent('on' + name, observer);
2313 } catch (e) {}
2318 /* prevent memory leaks in IE */
2319 if (navigator.appVersion.match(/\bMSIE\b/))
2320 Event.observe(window, 'unload', Event.unloadCache, false);
2321 var Position = {
2322 // set to true if needed, warning: firefox performance problems
2323 // NOT neeeded for page scrolling, only if draggable contained in
2324 // scrollable elements
2325 includeScrollOffsets: false,
2327 // must be called before calling withinIncludingScrolloffset, every time the
2328 // page is scrolled
2329 prepare: function() {
2330 this.deltaX = window.pageXOffset
2331 || document.documentElement.scrollLeft
2332 || document.body.scrollLeft
2333 || 0;
2334 this.deltaY = window.pageYOffset
2335 || document.documentElement.scrollTop
2336 || document.body.scrollTop
2337 || 0;
2340 realOffset: function(element) {
2341 var valueT = 0, valueL = 0;
2342 do {
2343 valueT += element.scrollTop || 0;
2344 valueL += element.scrollLeft || 0;
2345 element = element.parentNode;
2346 } while (element);
2347 return [valueL, valueT];
2350 cumulativeOffset: function(element) {
2351 var valueT = 0, valueL = 0;
2352 do {
2353 valueT += element.offsetTop || 0;
2354 valueL += element.offsetLeft || 0;
2355 element = element.offsetParent;
2356 } while (element);
2357 return [valueL, valueT];
2360 positionedOffset: function(element) {
2361 var valueT = 0, valueL = 0;
2362 do {
2363 valueT += element.offsetTop || 0;
2364 valueL += element.offsetLeft || 0;
2365 element = element.offsetParent;
2366 if (element) {
2367 if(element.tagName=='BODY') break;
2368 var p = Element.getStyle(element, 'position');
2369 if (p == 'relative' || p == 'absolute') break;
2371 } while (element);
2372 return [valueL, valueT];
2375 offsetParent: function(element) {
2376 if (element.offsetParent) return element.offsetParent;
2377 if (element == document.body) return element;
2379 while ((element = element.parentNode) && element != document.body)
2380 if (Element.getStyle(element, 'position') != 'static')
2381 return element;
2383 return document.body;
2386 // caches x/y coordinate pair to use with overlap
2387 within: function(element, x, y) {
2388 if (this.includeScrollOffsets)
2389 return this.withinIncludingScrolloffsets(element, x, y);
2390 this.xcomp = x;
2391 this.ycomp = y;
2392 this.offset = this.cumulativeOffset(element);
2394 return (y >= this.offset[1] &&
2395 y < this.offset[1] + element.offsetHeight &&
2396 x >= this.offset[0] &&
2397 x < this.offset[0] + element.offsetWidth);
2400 withinIncludingScrolloffsets: function(element, x, y) {
2401 var offsetcache = this.realOffset(element);
2403 this.xcomp = x + offsetcache[0] - this.deltaX;
2404 this.ycomp = y + offsetcache[1] - this.deltaY;
2405 this.offset = this.cumulativeOffset(element);
2407 return (this.ycomp >= this.offset[1] &&
2408 this.ycomp < this.offset[1] + element.offsetHeight &&
2409 this.xcomp >= this.offset[0] &&
2410 this.xcomp < this.offset[0] + element.offsetWidth);
2413 // within must be called directly before
2414 overlap: function(mode, element) {
2415 if (!mode) return 0;
2416 if (mode == 'vertical')
2417 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
2418 element.offsetHeight;
2419 if (mode == 'horizontal')
2420 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
2421 element.offsetWidth;
2424 page: function(forElement) {
2425 var valueT = 0, valueL = 0;
2427 var element = forElement;
2428 do {
2429 valueT += element.offsetTop || 0;
2430 valueL += element.offsetLeft || 0;
2432 // Safari fix
2433 if (element.offsetParent==document.body)
2434 if (Element.getStyle(element,'position')=='absolute') break;
2436 } while (element = element.offsetParent);
2438 element = forElement;
2439 do {
2440 if (!window.opera || element.tagName=='BODY') {
2441 valueT -= element.scrollTop || 0;
2442 valueL -= element.scrollLeft || 0;
2444 } while (element = element.parentNode);
2446 return [valueL, valueT];
2449 clone: function(source, target) {
2450 var options = Object.extend({
2451 setLeft: true,
2452 setTop: true,
2453 setWidth: true,
2454 setHeight: true,
2455 offsetTop: 0,
2456 offsetLeft: 0
2457 }, arguments[2] || {})
2459 // find page position of source
2460 source = $(source);
2461 var p = Position.page(source);
2463 // find coordinate system to use
2464 target = $(target);
2465 var delta = [0, 0];
2466 var parent = null;
2467 // delta [0,0] will do fine with position: fixed elements,
2468 // position:absolute needs offsetParent deltas
2469 if (Element.getStyle(target,'position') == 'absolute') {
2470 parent = Position.offsetParent(target);
2471 delta = Position.page(parent);
2474 // correct by body offsets (fixes Safari)
2475 if (parent == document.body) {
2476 delta[0] -= document.body.offsetLeft;
2477 delta[1] -= document.body.offsetTop;
2480 // set position
2481 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2482 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2483 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
2484 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
2487 absolutize: function(element) {
2488 element = $(element);
2489 if (element.style.position == 'absolute') return;
2490 Position.prepare();
2492 var offsets = Position.positionedOffset(element);
2493 var top = offsets[1];
2494 var left = offsets[0];
2495 var width = element.clientWidth;
2496 var height = element.clientHeight;
2498 element._originalLeft = left - parseFloat(element.style.left || 0);
2499 element._originalTop = top - parseFloat(element.style.top || 0);
2500 element._originalWidth = element.style.width;
2501 element._originalHeight = element.style.height;
2503 element.style.position = 'absolute';
2504 element.style.top = top + 'px';
2505 element.style.left = left + 'px';
2506 element.style.width = width + 'px';
2507 element.style.height = height + 'px';
2510 relativize: function(element) {
2511 element = $(element);
2512 if (element.style.position == 'relative') return;
2513 Position.prepare();
2515 element.style.position = 'relative';
2516 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2517 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2519 element.style.top = top + 'px';
2520 element.style.left = left + 'px';
2521 element.style.height = element._originalHeight;
2522 element.style.width = element._originalWidth;
2526 // Safari returns margins on body which is incorrect if the child is absolutely
2527 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2528 // KHTML/WebKit only.
2529 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
2530 Position.cumulativeOffset = function(element) {
2531 var valueT = 0, valueL = 0;
2532 do {
2533 valueT += element.offsetTop || 0;
2534 valueL += element.offsetLeft || 0;
2535 if (element.offsetParent == document.body)
2536 if (Element.getStyle(element, 'position') == 'absolute') break;
2538 element = element.offsetParent;
2539 } while (element);
2541 return [valueL, valueT];
2545 Element.addMethods();
2548 // ------------------------------------------------------------------------
2549 // ------------------------------------------------------------------------
2551 // The rest of this file is the actual ray tracer written by Adam
2552 // Burmister. It's a concatenation of the following files:
2554 // flog/color.js
2555 // flog/light.js
2556 // flog/vector.js
2557 // flog/ray.js
2558 // flog/scene.js
2559 // flog/material/basematerial.js
2560 // flog/material/solid.js
2561 // flog/material/chessboard.js
2562 // flog/shape/baseshape.js
2563 // flog/shape/sphere.js
2564 // flog/shape/plane.js
2565 // flog/intersectioninfo.js
2566 // flog/camera.js
2567 // flog/background.js
2568 // flog/engine.js
2571 /* Fake a Flog.* namespace */
2572 if(typeof(Flog) == 'undefined') var Flog = {};
2573 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2575 Flog.RayTracer.Color = Class.create();
2577 Flog.RayTracer.Color.prototype = {
2578 red : 0.0,
2579 green : 0.0,
2580 blue : 0.0,
2582 initialize : function(r, g, b) {
2583 if(!r) r = 0.0;
2584 if(!g) g = 0.0;
2585 if(!b) b = 0.0;
2587 this.red = r;
2588 this.green = g;
2589 this.blue = b;
2592 add : function(c1, c2){
2593 var result = new Flog.RayTracer.Color(0,0,0);
2595 result.red = c1.red + c2.red;
2596 result.green = c1.green + c2.green;
2597 result.blue = c1.blue + c2.blue;
2599 return result;
2602 addScalar: function(c1, s){
2603 var result = new Flog.RayTracer.Color(0,0,0);
2605 result.red = c1.red + s;
2606 result.green = c1.green + s;
2607 result.blue = c1.blue + s;
2609 result.limit();
2611 return result;
2614 subtract: function(c1, c2){
2615 var result = new Flog.RayTracer.Color(0,0,0);
2617 result.red = c1.red - c2.red;
2618 result.green = c1.green - c2.green;
2619 result.blue = c1.blue - c2.blue;
2621 return result;
2624 multiply : function(c1, c2) {
2625 var result = new Flog.RayTracer.Color(0,0,0);
2627 result.red = c1.red * c2.red;
2628 result.green = c1.green * c2.green;
2629 result.blue = c1.blue * c2.blue;
2631 return result;
2634 multiplyScalar : function(c1, f) {
2635 var result = new Flog.RayTracer.Color(0,0,0);
2637 result.red = c1.red * f;
2638 result.green = c1.green * f;
2639 result.blue = c1.blue * f;
2641 return result;
2644 divideFactor : function(c1, f) {
2645 var result = new Flog.RayTracer.Color(0,0,0);
2647 result.red = c1.red / f;
2648 result.green = c1.green / f;
2649 result.blue = c1.blue / f;
2651 return result;
2654 limit: function(){
2655 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0;
2656 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0;
2657 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0;
2660 distance : function(color) {
2661 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue);
2662 return d;
2665 blend: function(c1, c2, w){
2666 var result = new Flog.RayTracer.Color(0,0,0);
2667 result = Flog.RayTracer.Color.prototype.add(
2668 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w),
2669 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w)
2671 return result;
2674 brightness : function() {
2675 var r = Math.floor(this.red*255);
2676 var g = Math.floor(this.green*255);
2677 var b = Math.floor(this.blue*255);
2678 return (r * 77 + g * 150 + b * 29) >> 8;
2681 toString : function () {
2682 var r = Math.floor(this.red*255);
2683 var g = Math.floor(this.green*255);
2684 var b = Math.floor(this.blue*255);
2686 return "rgb("+ r +","+ g +","+ b +")";
2689 /* Fake a Flog.* namespace */
2690 if(typeof(Flog) == 'undefined') var Flog = {};
2691 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2693 Flog.RayTracer.Light = Class.create();
2695 Flog.RayTracer.Light.prototype = {
2696 position: null,
2697 color: null,
2698 intensity: 10.0,
2700 initialize : function(pos, color, intensity) {
2701 this.position = pos;
2702 this.color = color;
2703 this.intensity = (intensity ? intensity : 10.0);
2706 getIntensity: function(distance){
2707 if(distance >= intensity) return 0;
2709 return Math.pow((intensity - distance) / strength, 0.2);
2712 toString : function () {
2713 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
2716 /* Fake a Flog.* namespace */
2717 if(typeof(Flog) == 'undefined') var Flog = {};
2718 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2720 Flog.RayTracer.Vector = Class.create();
2722 Flog.RayTracer.Vector.prototype = {
2723 x : 0.0,
2724 y : 0.0,
2725 z : 0.0,
2727 initialize : function(x, y, z) {
2728 this.x = (x ? x : 0);
2729 this.y = (y ? y : 0);
2730 this.z = (z ? z : 0);
2733 copy: function(vector){
2734 this.x = vector.x;
2735 this.y = vector.y;
2736 this.z = vector.z;
2739 normalize : function() {
2740 var m = this.magnitude();
2741 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
2744 magnitude : function() {
2745 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
2748 cross : function(w) {
2749 return new Flog.RayTracer.Vector(
2750 -this.z * w.y + this.y * w.z,
2751 this.z * w.x - this.x * w.z,
2752 -this.y * w.x + this.x * w.y);
2755 dot : function(w) {
2756 return this.x * w.x + this.y * w.y + this.z * w.z;
2759 add : function(v, w) {
2760 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
2763 subtract : function(v, w) {
2764 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
2765 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
2768 multiplyVector : function(v, w) {
2769 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
2772 multiplyScalar : function(v, w) {
2773 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
2776 toString : function () {
2777 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
2780 /* Fake a Flog.* namespace */
2781 if(typeof(Flog) == 'undefined') var Flog = {};
2782 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2784 Flog.RayTracer.Ray = Class.create();
2786 Flog.RayTracer.Ray.prototype = {
2787 position : null,
2788 direction : null,
2789 initialize : function(pos, dir) {
2790 this.position = pos;
2791 this.direction = dir;
2794 toString : function () {
2795 return 'Ray [' + this.position + ',' + this.direction + ']';
2798 /* Fake a Flog.* namespace */
2799 if(typeof(Flog) == 'undefined') var Flog = {};
2800 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2802 Flog.RayTracer.Scene = Class.create();
2804 Flog.RayTracer.Scene.prototype = {
2805 camera : null,
2806 shapes : [],
2807 lights : [],
2808 background : null,
2810 initialize : function() {
2811 this.camera = new Flog.RayTracer.Camera(
2812 new Flog.RayTracer.Vector(0,0,-5),
2813 new Flog.RayTracer.Vector(0,0,1),
2814 new Flog.RayTracer.Vector(0,1,0)
2816 this.shapes = new Array();
2817 this.lights = new Array();
2818 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
2821 /* Fake a Flog.* namespace */
2822 if(typeof(Flog) == 'undefined') var Flog = {};
2823 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2824 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
2826 Flog.RayTracer.Material.BaseMaterial = Class.create();
2828 Flog.RayTracer.Material.BaseMaterial.prototype = {
2830 gloss: 2.0, // [0...infinity] 0 = matt
2831 transparency: 0.0, // 0=opaque
2832 reflection: 0.0, // [0...infinity] 0 = no reflection
2833 refraction: 0.50,
2834 hasTexture: false,
2836 initialize : function() {
2840 getColor: function(u, v){
2844 wrapUp: function(t){
2845 t = t % 2.0;
2846 if(t < -1) t += 2.0;
2847 if(t >= 1) t -= 2.0;
2848 return t;
2851 toString : function () {
2852 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2855 /* Fake a Flog.* namespace */
2856 if(typeof(Flog) == 'undefined') var Flog = {};
2857 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2859 Flog.RayTracer.Material.Solid = Class.create();
2861 Flog.RayTracer.Material.Solid.prototype = Object.extend(
2862 new Flog.RayTracer.Material.BaseMaterial(), {
2863 initialize : function(color, reflection, refraction, transparency, gloss) {
2864 this.color = color;
2865 this.reflection = reflection;
2866 this.transparency = transparency;
2867 this.gloss = gloss;
2868 this.hasTexture = false;
2871 getColor: function(u, v){
2872 return this.color;
2875 toString : function () {
2876 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2880 /* Fake a Flog.* namespace */
2881 if(typeof(Flog) == 'undefined') var Flog = {};
2882 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2884 Flog.RayTracer.Material.Chessboard = Class.create();
2886 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
2887 new Flog.RayTracer.Material.BaseMaterial(), {
2888 colorEven: null,
2889 colorOdd: null,
2890 density: 0.5,
2892 initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
2893 this.colorEven = colorEven;
2894 this.colorOdd = colorOdd;
2895 this.reflection = reflection;
2896 this.transparency = transparency;
2897 this.gloss = gloss;
2898 this.density = density;
2899 this.hasTexture = true;
2902 getColor: function(u, v){
2903 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
2905 if(t < 0.0)
2906 return this.colorEven;
2907 else
2908 return this.colorOdd;
2911 toString : function () {
2912 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2916 /* Fake a Flog.* namespace */
2917 if(typeof(Flog) == 'undefined') var Flog = {};
2918 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2919 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2921 Flog.RayTracer.Shape.BaseShape = Class.create();
2923 Flog.RayTracer.Shape.BaseShape.prototype = {
2924 position: null,
2925 material: null,
2927 initialize : function() {
2928 this.position = new Vector(0,0,0);
2929 this.material = new Flog.RayTracer.Material.SolidMaterial(
2930 new Flog.RayTracer.Color(1,0,1),
2937 toString : function () {
2938 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2941 /* Fake a Flog.* namespace */
2942 if(typeof(Flog) == 'undefined') var Flog = {};
2943 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2944 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2946 Flog.RayTracer.Shape.Sphere = Class.create();
2948 Flog.RayTracer.Shape.Sphere.prototype = {
2949 initialize : function(pos, radius, material) {
2950 this.radius = radius;
2951 this.position = pos;
2952 this.material = material;
2955 intersect: function(ray){
2956 var info = new Flog.RayTracer.IntersectionInfo();
2957 info.shape = this;
2959 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
2961 var B = dst.dot(ray.direction);
2962 var C = dst.dot(dst) - (this.radius * this.radius);
2963 var D = (B * B) - C;
2965 if(D > 0){ // intersection!
2966 info.isHit = true;
2967 info.distance = (-B) - Math.sqrt(D);
2968 info.position = Flog.RayTracer.Vector.prototype.add(
2969 ray.position,
2970 Flog.RayTracer.Vector.prototype.multiplyScalar(
2971 ray.direction,
2972 info.distance
2975 info.normal = Flog.RayTracer.Vector.prototype.subtract(
2976 info.position,
2977 this.position
2978 ).normalize();
2980 info.color = this.material.getColor(0,0);
2981 } else {
2982 info.isHit = false;
2984 return info;
2987 toString : function () {
2988 return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
2991 /* Fake a Flog.* namespace */
2992 if(typeof(Flog) == 'undefined') var Flog = {};
2993 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2994 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2996 Flog.RayTracer.Shape.Plane = Class.create();
2998 Flog.RayTracer.Shape.Plane.prototype = {
2999 d: 0.0,
3001 initialize : function(pos, d, material) {
3002 this.position = pos;
3003 this.d = d;
3004 this.material = material;
3007 intersect: function(ray){
3008 var info = new Flog.RayTracer.IntersectionInfo();
3010 var Vd = this.position.dot(ray.direction);
3011 if(Vd == 0) return info; // no intersection
3013 var t = -(this.position.dot(ray.position) + this.d) / Vd;
3014 if(t <= 0) return info;
3016 info.shape = this;
3017 info.isHit = true;
3018 info.position = Flog.RayTracer.Vector.prototype.add(
3019 ray.position,
3020 Flog.RayTracer.Vector.prototype.multiplyScalar(
3021 ray.direction,
3025 info.normal = this.position;
3026 info.distance = t;
3028 if(this.material.hasTexture){
3029 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
3030 var vV = vU.cross(this.position);
3031 var u = info.position.dot(vU);
3032 var v = info.position.dot(vV);
3033 info.color = this.material.getColor(u,v);
3034 } else {
3035 info.color = this.material.getColor(0,0);
3038 return info;
3041 toString : function () {
3042 return 'Plane [' + this.position + ', d=' + this.d + ']';
3045 /* Fake a Flog.* namespace */
3046 if(typeof(Flog) == 'undefined') var Flog = {};
3047 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3049 Flog.RayTracer.IntersectionInfo = Class.create();
3051 Flog.RayTracer.IntersectionInfo.prototype = {
3052 isHit: false,
3053 hitCount: 0,
3054 shape: null,
3055 position: null,
3056 normal: null,
3057 color: null,
3058 distance: null,
3060 initialize : function() {
3061 this.color = new Flog.RayTracer.Color(0,0,0);
3064 toString : function () {
3065 return 'Intersection [' + this.position + ']';
3068 /* Fake a Flog.* namespace */
3069 if(typeof(Flog) == 'undefined') var Flog = {};
3070 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3072 Flog.RayTracer.Camera = Class.create();
3074 Flog.RayTracer.Camera.prototype = {
3075 position: null,
3076 lookAt: null,
3077 equator: null,
3078 up: null,
3079 screen: null,
3081 initialize : function(pos, lookAt, up) {
3082 this.position = pos;
3083 this.lookAt = lookAt;
3084 this.up = up;
3085 this.equator = lookAt.normalize().cross(this.up);
3086 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
3089 getRay: function(vx, vy){
3090 var pos = Flog.RayTracer.Vector.prototype.subtract(
3091 this.screen,
3092 Flog.RayTracer.Vector.prototype.subtract(
3093 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
3094 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
3097 pos.y = pos.y * -1;
3098 var dir = Flog.RayTracer.Vector.prototype.subtract(
3099 pos,
3100 this.position
3103 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
3105 return ray;
3108 toString : function () {
3109 return 'Ray []';
3112 /* Fake a Flog.* namespace */
3113 if(typeof(Flog) == 'undefined') var Flog = {};
3114 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3116 Flog.RayTracer.Background = Class.create();
3118 Flog.RayTracer.Background.prototype = {
3119 color : null,
3120 ambience : 0.0,
3122 initialize : function(color, ambience) {
3123 this.color = color;
3124 this.ambience = ambience;
3127 /* Fake a Flog.* namespace */
3128 if(typeof(Flog) == 'undefined') var Flog = {};
3129 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3131 Flog.RayTracer.Engine = Class.create();
3133 Flog.RayTracer.Engine.prototype = {
3134 canvas: null, /* 2d context we can render to */
3136 initialize: function(options){
3137 this.options = Object.extend({
3138 canvasHeight: 100,
3139 canvasWidth: 100,
3140 pixelWidth: 2,
3141 pixelHeight: 2,
3142 renderDiffuse: false,
3143 renderShadows: false,
3144 renderHighlights: false,
3145 renderReflections: false,
3146 rayDepth: 2
3147 }, options || {});
3149 this.options.canvasHeight /= this.options.pixelHeight;
3150 this.options.canvasWidth /= this.options.pixelWidth;
3152 /* TODO: dynamically include other scripts */
3155 setPixel: function(x, y, color){
3156 var pxW, pxH;
3157 pxW = this.options.pixelWidth;
3158 pxH = this.options.pixelHeight;
3160 if (this.canvas) {
3161 this.canvas.fillStyle = color.toString();
3162 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
3163 } else {
3164 if (x === y) {
3165 checkNumber += color.brightness();
3167 // print(x * pxW, y * pxH, pxW, pxH);
3171 renderScene: function(scene, canvas){
3172 checkNumber = 0;
3173 /* Get canvas */
3174 if (canvas) {
3175 this.canvas = canvas.getContext("2d");
3176 } else {
3177 this.canvas = null;
3180 var canvasHeight = this.options.canvasHeight;
3181 var canvasWidth = this.options.canvasWidth;
3183 for(var y=0; y < canvasHeight; y++){
3184 for(var x=0; x < canvasWidth; x++){
3185 var yp = y * 1.0 / canvasHeight * 2 - 1;
3186 var xp = x * 1.0 / canvasWidth * 2 - 1;
3188 var ray = scene.camera.getRay(xp, yp);
3190 var color = this.getPixelColor(ray, scene);
3192 this.setPixel(x, y, color);
3195 if (checkNumber !== 2321) {
3196 throw new Error("Scene rendered incorrectly");
3200 getPixelColor: function(ray, scene){
3201 var info = this.testIntersection(ray, scene, null);
3202 if(info.isHit){
3203 var color = this.rayTrace(info, ray, scene, 0);
3204 return color;
3206 return scene.background.color;
3209 testIntersection: function(ray, scene, exclude){
3210 var hits = 0;
3211 var best = new Flog.RayTracer.IntersectionInfo();
3212 best.distance = 2000;
3214 for(var i=0; i<scene.shapes.length; i++){
3215 var shape = scene.shapes[i];
3217 if(shape != exclude){
3218 var info = shape.intersect(ray);
3219 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
3220 best = info;
3221 hits++;
3225 best.hitCount = hits;
3226 return best;
3229 getReflectionRay: function(P,N,V){
3230 var c1 = -N.dot(V);
3231 var R1 = Flog.RayTracer.Vector.prototype.add(
3232 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
3235 return new Flog.RayTracer.Ray(P, R1);
3238 rayTrace: function(info, ray, scene, depth){
3239 // Calc ambient
3240 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
3241 var oldColor = color;
3242 var shininess = Math.pow(10, info.shape.material.gloss + 1);
3244 for(var i=0; i<scene.lights.length; i++){
3245 var light = scene.lights[i];
3247 // Calc diffuse lighting
3248 var v = Flog.RayTracer.Vector.prototype.subtract(
3249 light.position,
3250 info.position
3251 ).normalize();
3253 if(this.options.renderDiffuse){
3254 var L = v.dot(info.normal);
3255 if(L > 0.0){
3256 color = Flog.RayTracer.Color.prototype.add(
3257 color,
3258 Flog.RayTracer.Color.prototype.multiply(
3259 info.color,
3260 Flog.RayTracer.Color.prototype.multiplyScalar(
3261 light.color,
3269 // The greater the depth the more accurate the colours, but
3270 // this is exponentially (!) expensive
3271 if(depth <= this.options.rayDepth){
3272 // calculate reflection ray
3273 if(this.options.renderReflections && info.shape.material.reflection > 0)
3275 var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
3276 var refl = this.testIntersection(reflectionRay, scene, info.shape);
3278 if (refl.isHit && refl.distance > 0){
3279 refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
3280 } else {
3281 refl.color = scene.background.color;
3284 color = Flog.RayTracer.Color.prototype.blend(
3285 color,
3286 refl.color,
3287 info.shape.material.reflection
3291 // Refraction
3292 /* TODO */
3295 /* Render shadows and highlights */
3297 var shadowInfo = new Flog.RayTracer.IntersectionInfo();
3299 if(this.options.renderShadows){
3300 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
3302 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
3303 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
3304 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
3305 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
3306 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
3310 // Phong specular highlights
3311 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
3312 var Lv = Flog.RayTracer.Vector.prototype.subtract(
3313 info.shape.position,
3314 light.position
3315 ).normalize();
3317 var E = Flog.RayTracer.Vector.prototype.subtract(
3318 scene.camera.position,
3319 info.shape.position
3320 ).normalize();
3322 var H = Flog.RayTracer.Vector.prototype.subtract(
3325 ).normalize();
3327 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
3328 color = Flog.RayTracer.Color.prototype.add(
3329 Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
3330 color
3334 color.limit();
3335 return color;
3340 function renderScene(){
3341 var scene = new Flog.RayTracer.Scene();
3343 scene.camera = new Flog.RayTracer.Camera(
3344 new Flog.RayTracer.Vector(0, 0, -15),
3345 new Flog.RayTracer.Vector(-0.2, 0, 5),
3346 new Flog.RayTracer.Vector(0, 1, 0)
3349 scene.background = new Flog.RayTracer.Background(
3350 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
3354 var sphere = new Flog.RayTracer.Shape.Sphere(
3355 new Flog.RayTracer.Vector(-1.5, 1.5, 2),
3356 1.5,
3357 new Flog.RayTracer.Material.Solid(
3358 new Flog.RayTracer.Color(0,0.5,0.5),
3359 0.3,
3360 0.0,
3361 0.0,
3366 var sphere1 = new Flog.RayTracer.Shape.Sphere(
3367 new Flog.RayTracer.Vector(1, 0.25, 1),
3368 0.5,
3369 new Flog.RayTracer.Material.Solid(
3370 new Flog.RayTracer.Color(0.9,0.9,0.9),
3371 0.1,
3372 0.0,
3373 0.0,
3378 var plane = new Flog.RayTracer.Shape.Plane(
3379 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
3380 1.2,
3381 new Flog.RayTracer.Material.Chessboard(
3382 new Flog.RayTracer.Color(1,1,1),
3383 new Flog.RayTracer.Color(0,0,0),
3384 0.2,
3385 0.0,
3386 1.0,
3391 scene.shapes.push(plane);
3392 scene.shapes.push(sphere);
3393 scene.shapes.push(sphere1);
3395 var light = new Flog.RayTracer.Light(
3396 new Flog.RayTracer.Vector(5, 10, -1),
3397 new Flog.RayTracer.Color(0.8, 0.8, 0.8)
3400 var light1 = new Flog.RayTracer.Light(
3401 new Flog.RayTracer.Vector(-3, 5, -15),
3402 new Flog.RayTracer.Color(0.8, 0.8, 0.8),
3406 scene.lights.push(light);
3407 scene.lights.push(light1);
3409 var imageWidth = 100; // $F('imageWidth');
3410 var imageHeight = 100; // $F('imageHeight');
3411 var pixelSize = "5,5".split(','); // $F('pixelSize').split(',');
3412 var renderDiffuse = true; // $F('renderDiffuse');
3413 var renderShadows = true; // $F('renderShadows');
3414 var renderHighlights = true; // $F('renderHighlights');
3415 var renderReflections = true; // $F('renderReflections');
3416 var rayDepth = 2;//$F('rayDepth');
3418 var raytracer = new Flog.RayTracer.Engine(
3420 canvasWidth: imageWidth,
3421 canvasHeight: imageHeight,
3422 pixelWidth: pixelSize[0],
3423 pixelHeight: pixelSize[1],
3424 "renderDiffuse": renderDiffuse,
3425 "renderHighlights": renderHighlights,
3426 "renderShadows": renderShadows,
3427 "renderReflections": renderReflections,
3428 "rayDepth": rayDepth
3432 raytracer.renderScene(scene, null, 0);
3435 window.onload = function(){
3436 startTest("v8-raytrace", '39e09d10');
3438 test("RayTrace", renderScene);
3440 endTest();
3442 </script>
3443 </body>
3444 </html>