3 <script src="../htmlrunner.js"></script>
5 // The ray tracer code in this file is written by Adam Burmister. It
6 // is available in its original form from:
8 // http://labs.flog.nz.co/raytracer/
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.
18 // Create dummy objects if we're not running in a browser.
19 if (typeof document == 'undefined') {
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 //--------------------
42 XPath: !!document.evaluate
45 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
46 emptyFunction: function() {},
47 K: function(x) { return x }
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];
67 Object.extend(Object, {
68 inspect: function(object) {
70 if (object === undefined) return 'undefined';
71 if (object === null) return 'null';
72 return object.inspect ? object.inspect() : object.toString();
74 if (e instanceof RangeError) return '...';
79 keys: function(object) {
81 for (var property in object)
86 values: function(object) {
88 for (var property in object)
89 values.push(object[property]);
93 clone: function(object) {
94 return Object.extend({}, object);
98 Function.prototype.bind = function() {
99 var __method = this, args = $A(arguments), object = args.shift();
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;
123 times: function(iterator) {
124 $R(0, this, true).each(iterator);
133 for (var i = 0, length = arguments.length; i < length; i++) {
134 var lambda = arguments[i];
136 returnValue = lambda();
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);
162 if (!this.timer) return;
163 clearInterval(this.timer);
167 onTimerEvent: function() {
168 if (!this.currentlyExecuting) {
170 this.currentlyExecuting = true;
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);
193 result += source, source = '';
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);
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;
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;
280 toArray: function() {
281 return this.split('');
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)
297 for (var i = 1; i < len; i++)
298 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
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, '\\\\');
318 return '"' + escapedString.replace(/"/g, '\\"') + '"';
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();
353 each: function(iterator) {
356 this._each(function(value) {
358 iterator(value, index++);
360 if (e != $continue) throw e;
364 if (e != $break) throw e;
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) {
378 this.each(function(value, index) {
379 result = result && !!(iterator || Prototype.K)(value, index);
380 if (!result) throw $break;
385 any: function(iterator) {
387 this.each(function(value, index) {
388 if (result = !!(iterator || Prototype.K)(value, index))
394 collect: function(iterator) {
396 this.each(function(value, index) {
397 results.push((iterator || Prototype.K)(value, index));
402 detect: function(iterator) {
404 this.each(function(value, index) {
405 if (iterator(value, index)) {
413 findAll: function(iterator) {
415 this.each(function(value, index) {
416 if (iterator(value, index))
422 grep: function(pattern, iterator) {
424 this.each(function(value, index) {
425 var stringValue = value.toString();
426 if (stringValue.match(pattern))
427 results.push((iterator || Prototype.K)(value, index));
432 include: function(object) {
434 this.each(function(value) {
435 if (value == object) {
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);
451 inject: function(memo, iterator) {
452 this.each(function(value, index) {
453 memo = iterator(memo, value, index);
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) {
467 this.each(function(value, index) {
468 value = (iterator || Prototype.K)(value, index);
469 if (result == undefined || value >= result)
475 min: function(iterator) {
477 this.each(function(value, index) {
478 value = (iterator || Prototype.K)(value, index);
479 if (result == undefined || value < 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) {
496 this.each(function(value, index) {
497 results.push(value[property]);
502 reject: function(iterator) {
504 this.each(function(value, index) {
505 if (!iterator(value, index))
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;
520 toArray: 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));
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();
557 for (var i = 0, length = iterable.length; i < length; i++)
558 results.push(iterable[i]);
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++)
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;
613 reverse: function(inline) {
614 return (inline !== false ? this : this.toArray())._reverse();
618 return this.length > 1 ? this : this[0];
622 return this.inject([], function(array, value) {
623 return array.include(value) ? array : array.concat([value]);
628 return [].concat(this);
635 inspect: function() {
636 return '[' + this.map(Object.inspect).join(', ') + ']';
640 Array.prototype.toArray = Array.prototype.clone;
643 string = string.strip();
644 return string ? string.split(/\s+/) : [];
648 Array.prototype.concat = function(){
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]);
656 array.push(arguments[i]);
662 var Hash = function(obj) {
663 Object.extend(this, obj || {});
666 Object.extend(Hash, {
667 toQueryString: function(obj) {
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();
677 key = encodeURIComponent(pair.key);
678 values.each(function(value) {
679 value = value != undefined ? encodeURIComponent(value) : '';
680 parts.push(key + '=' + encodeURIComponent(value));
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];
708 return this.pluck('key');
712 return this.pluck('value');
715 merge: function(hash) {
716 return $H(hash).inject(this, function(mergedHash, pair) {
717 mergedHash[pair.key] = pair.value;
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;
729 if (result.constructor != Array) result = [result];
733 delete this[arguments[i]];
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) {
759 this.exclusive = exclusive;
762 _each: function(iterator) {
763 var value = this.start;
764 while (this.include(value)) {
766 value = value.succ();
770 include: function(value) {
771 if (value < this.start)
774 return value < this.end;
775 return value <= this.end;
779 var $R = function(start, end, exclusive) {
780 return new ObjectRange(start, end, exclusive);
784 getTransport: function() {
786 function() {return new XMLHttpRequest()},
787 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
788 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
792 activeRequestCount: 0
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') {
815 responder[callback].apply(responder, [request, transport, json]);
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) {
839 contentType: 'application/x-www-form-urlencoded',
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(), {
858 initialize: function(url, options) {
859 this.transport = Ajax.getTransport();
860 this.setOptions(options);
864 request: function(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;
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();
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() {
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];
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') {
960 this._complete = true;
961 (this.options['on' + this.transport.status]
962 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
963 || Prototype.emptyFunction)(transport, json);
965 this.dispatchException(e);
968 if ((this.getHeader('Content-type') || 'text/javascript').strip().
969 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
974 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
975 Ajax.Responders.dispatch('on' + state, this, transport, json);
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) {
988 return this.transport.getResponseHeader(name);
989 } catch (e) { return null }
992 evalJSON: function() {
994 var json = this.getHeader('X-JSON');
995 return json ? eval('(' + json + ')') : null;
996 } catch (e) { return null }
999 evalResponse: function() {
1001 return eval(this.transport.responseText);
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) {
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);
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);
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);
1064 this.container = container;
1071 this.options.onComplete = this.updateComplete.bind(this);
1072 this.onTimerEvent();
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]));
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) {
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));
1118 document.getElementsByClassName = function(className, parentElement) {
1119 if (Prototype.BrowserFeatures.XPath) {
1120 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1121 return document._getElementsByXPath(q, parentElement);
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));
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;
1163 Element.extend.cache = {
1164 findOrStore: function(value) {
1165 return this[value] = this[value] || function() {
1166 return value.apply(null, [this].concat($A(arguments)));
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);
1182 hide: function(element) {
1183 $(element).style.display = 'none';
1187 show: function(element) {
1188 $(element).style.display = '';
1192 remove: function(element) {
1193 element = $(element);
1194 element.parentNode.removeChild(element);
1198 update: function(element, html) {
1199 html = typeof html == 'undefined' ? '' : html.toString();
1200 $(element).innerHTML = html.stripScripts();
1201 setTimeout(function() {html.evalScripts()}, 10);
1205 replace: function(element, html) {
1206 element = $(element);
1207 html = typeof html == 'undefined' ? '' : html.toString();
1208 if (element.outerHTML) {
1209 element.outerHTML = html.stripScripts();
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);
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);
1234 while (element = element[property])
1235 if (element.nodeType == 1)
1236 elements.push(Element.extend(element));
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());
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|$)")))
1333 addClassName: function(element, className) {
1334 if (!(element = $(element))) return;
1335 Element.classNames(element).add(className);
1339 removeClassName: function(element, className) {
1340 if (!(element = $(element))) return;
1341 Element.classNames(element).remove(className);
1345 toggleClassName: function(element, className) {
1346 if (!(element = $(element))) return;
1347 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
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;
1366 var nextNode = node.nextSibling;
1367 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1368 element.removeChild(node);
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;
1385 scrollTo: function(element) {
1386 element = $(element);
1387 var pos = Position.cumulativeOffset(element);
1388 window.scrollTo(pos[0], pos[1]);
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];
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;
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') {
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,'');
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;
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
1478 element.style.top = 0;
1479 element.style.left = 0;
1485 undoPositioned: function(element) {
1486 element = $(element);
1487 if (element._madePositioned) {
1488 element._madePositioned = undefined;
1489 element.style.position =
1491 element.style.left =
1492 element.style.bottom =
1493 element.style.right = '';
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';
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;
1516 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
1518 Element._attributeTranslations = {};
1520 Element._attributeTranslations.names = {
1524 datetime: "dateTime",
1525 accesskey: "accessKey",
1526 tabindex: "tabIndex",
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');
1580 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1584 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1588 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
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) });
1599 element.innerHTML = html.stripScripts();
1601 setTimeout(function() {html.evalScripts()}, 10);
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) {
1658 this.element.insertAdjacentHTML(this.adjacency, this.content);
1660 var tagName = this.element.tagName.toUpperCase();
1661 if (['TBODY', 'TR'].include(tagName)) {
1662 this.insertContent(this.contentFromAnonymousTable());
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);
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);
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);
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);
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;
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] || ''});
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];
1800 case '#': params.id = clause; break;
1801 case '.': params.classNames.push(clause); break;
1803 case undefined: params.tagName = clause.toUpperCase(); break;
1804 default: abort(expr.inspect());
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()
1837 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
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) {
1857 if (element = $(this.params.id))
1858 if (this.match(element))
1859 if (!scope || Element.childOf(element, scope))
1862 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
1865 for (var i = 0, length = scope.length; i < length; i++)
1866 if (this.match(element = scope[i]))
1867 results.push(Element.extend(element));
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));
1901 return Selector.findChildElements(document, $A(arguments));
1904 reset: function(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) {
1915 if (result[key].constructor != Array) result[key] = [result[key]];
1916 result[key].push(value);
1918 else result[key] = value;
1924 return getHash ? data : Hash.toQueryString(data);
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));
1943 getInputs: function(form, typeName, name) {
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))
1953 matchingInputs.push(Element.extend(input));
1956 return matchingInputs;
1959 disable: function(form) {
1961 form.getElements().each(function(element) {
1963 element.disabled = 'true';
1968 enable: function(form) {
1970 form.getElements().each(function(element) {
1971 element.disabled = '';
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) {
1985 form.findFirstElement().activate();
1990 Object.extend(Form, Form.Methods);
1992 /*--------------------------------------------------------------------------*/
1995 focus: function(element) {
2000 select: function(element) {
2001 $(element).select();
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) {
2013 pair[element.name] = value;
2014 return Hash.toQueryString(pair);
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 = '';
2031 present: function(element) {
2032 return $(element).value != '';
2035 activate: function(element) {
2036 element = $(element);
2038 if (element.select && ( element.tagName.toLowerCase() != 'input' ||
2039 !['button', 'reset', 'submit'].include(element.type) ) )
2044 disable: function(element) {
2045 element = $(element);
2046 element.disabled = true;
2050 enable: function(element) {
2051 element = $(element);
2053 element.disabled = false;
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()) {
2069 return Form.Element.Serializers.inputSelector(element);
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));
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));
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();
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) {
2181 switch (element.type.toLowerCase()) {
2184 Event.observe(element, 'click', this.onElementEvent.bind(this));
2187 Event.observe(element, 'change', this.onElementEvent.bind(this));
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, {
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();
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;
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))
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))
2308 if (element.removeEventListener) {
2309 element.removeEventListener(name, observer, useCapture);
2310 } else if (element.detachEvent) {
2312 element.detachEvent('on' + name, observer);
2318 /* prevent memory leaks in IE */
2319 if (navigator.appVersion.match(/\bMSIE\b/))
2320 Event.observe(window, 'unload', Event.unloadCache, false);
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
2329 prepare: function() {
2330 this.deltaX = window.pageXOffset
2331 || document.documentElement.scrollLeft
2332 || document.body.scrollLeft
2334 this.deltaY = window.pageYOffset
2335 || document.documentElement.scrollTop
2336 || document.body.scrollTop
2340 realOffset: function(element) {
2341 var valueT = 0, valueL = 0;
2343 valueT += element.scrollTop || 0;
2344 valueL += element.scrollLeft || 0;
2345 element = element.parentNode;
2347 return [valueL, valueT];
2350 cumulativeOffset: function(element) {
2351 var valueT = 0, valueL = 0;
2353 valueT += element.offsetTop || 0;
2354 valueL += element.offsetLeft || 0;
2355 element = element.offsetParent;
2357 return [valueL, valueT];
2360 positionedOffset: function(element) {
2361 var valueT = 0, valueL = 0;
2363 valueT += element.offsetTop || 0;
2364 valueL += element.offsetLeft || 0;
2365 element = element.offsetParent;
2367 if(element.tagName=='BODY') break;
2368 var p = Element.getStyle(element, 'position');
2369 if (p == 'relative' || p == 'absolute') break;
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')
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);
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;
2429 valueT += element.offsetTop || 0;
2430 valueL += element.offsetLeft || 0;
2433 if (element.offsetParent==document.body)
2434 if (Element.getStyle(element,'position')=='absolute') break;
2436 } while (element = element.offsetParent);
2438 element = forElement;
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({
2457 }, arguments[2] || {})
2459 // find page position of source
2461 var p = Position.page(source);
2463 // find coordinate system to use
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;
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;
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;
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;
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;
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:
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
2567 // flog/background.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 = {
2582 initialize : function(r, g, 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;
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;
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;
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;
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;
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;
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);
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)
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 = {
2700 initialize : function(pos, color, intensity) {
2701 this.position = pos;
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 = {
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){
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);
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 = {
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 = {
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
2836 initialize : function() {
2840 getColor: function(u, v){
2844 wrapUp: function(t){
2846 if(t < -1) t += 2.0;
2847 if(t >= 1) t -= 2.0;
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) {
2865 this.reflection = reflection;
2866 this.transparency = transparency;
2868 this.hasTexture = false;
2871 getColor: function(u, v){
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(), {
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;
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);
2906 return this.colorEven;
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 = {
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();
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!
2967 info.distance = (-B) - Math.sqrt(D);
2968 info.position = Flog.RayTracer.Vector.prototype.add(
2970 Flog.RayTracer.Vector.prototype.multiplyScalar(
2975 info.normal = Flog.RayTracer.Vector.prototype.subtract(
2980 info.color = this.material.getColor(0,0);
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 = {
3001 initialize : function(pos, d, material) {
3002 this.position = pos;
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;
3018 info.position = Flog.RayTracer.Vector.prototype.add(
3020 Flog.RayTracer.Vector.prototype.multiplyScalar(
3025 info.normal = this.position;
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);
3035 info.color = this.material.getColor(0,0);
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 = {
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 = {
3081 initialize : function(pos, lookAt, up) {
3082 this.position = pos;
3083 this.lookAt = lookAt;
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(
3092 Flog.RayTracer.Vector.prototype.subtract(
3093 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
3094 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
3098 var dir = Flog.RayTracer.Vector.prototype.subtract(
3103 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
3108 toString : function () {
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 = {
3122 initialize : function(color, ambience) {
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({
3142 renderDiffuse: false,
3143 renderShadows: false,
3144 renderHighlights: false,
3145 renderReflections: false,
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){
3157 pxW = this.options.pixelWidth;
3158 pxH = this.options.pixelHeight;
3161 this.canvas.fillStyle = color.toString();
3162 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
3165 checkNumber += color.brightness();
3167 // print(x * pxW, y * pxH, pxW, pxH);
3171 renderScene: function(scene, canvas){
3175 this.canvas = canvas.getContext("2d");
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);
3203 var color = this.rayTrace(info, ray, scene, 0);
3206 return scene.background.color;
3209 testIntersection: function(ray, scene, exclude){
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){
3225 best.hitCount = hits;
3229 getReflectionRay: function(P,N,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){
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(
3253 if(this.options.renderDiffuse){
3254 var L = v.dot(info.normal);
3256 color = Flog.RayTracer.Color.prototype.add(
3258 Flog.RayTracer.Color.prototype.multiply(
3260 Flog.RayTracer.Color.prototype.multiplyScalar(
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);
3281 refl.color = scene.background.color;
3284 color = Flog.RayTracer.Color.prototype.blend(
3287 info.shape.material.reflection
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,
3317 var E = Flog.RayTracer.Vector.prototype.subtract(
3318 scene.camera.position,
3322 var H = Flog.RayTracer.Vector.prototype.subtract(
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),
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),
3357 new Flog.RayTracer.Material.Solid(
3358 new Flog.RayTracer.Color(0,0.5,0.5),
3366 var sphere1 = new Flog.RayTracer.Shape.Sphere(
3367 new Flog.RayTracer.Vector(1, 0.25, 1),
3369 new Flog.RayTracer.Material.Solid(
3370 new Flog.RayTracer.Color(0.9,0.9,0.9),
3378 var plane = new Flog.RayTracer.Shape.Plane(
3379 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
3381 new Flog.RayTracer.Material.Chessboard(
3382 new Flog.RayTracer.Color(1,1,1),
3383 new Flog.RayTracer.Color(0,0,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);