1 // The ray tracer code in this file is written by Adam Burmister. It
2 // is available in its original form from:
4 // http://labs.flog.nz.co/raytracer/
6 // It has been modified slightly by Google to work as a standalone
7 // benchmark, but the all the computational code remains
8 // untouched. This file also contains a copy of the Prototype
9 // JavaScript framework which is used by the ray tracer.
11 var RayTrace = new BenchmarkSuite('RayTrace', 932666, [
12 new Benchmark('RayTrace', renderScene)
16 // Create dummy objects if we're not running in a browser.
17 if (typeof document == 'undefined') {
19 window = { opera: null };
20 navigator = { userAgent: null, appVersion: "" };
24 // ------------------------------------------------------------------------
25 // ------------------------------------------------------------------------
28 /* Prototype JavaScript framework, version 1.5.0
29 * (c) 2005-2007 Sam Stephenson
31 * Prototype is freely distributable under the terms of an MIT-style license.
32 * For details, see the Prototype web site: http://prototype.conio.net/
34 /*--------------------------------------------------------------------------*/
36 //--------------------
40 XPath: !!document.evaluate
43 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
44 emptyFunction: function() {},
45 K: function(x) { return x }
51 this.initialize.apply(this, arguments);
56 var Abstract = new Object();
58 Object.extend = function(destination, source) {
59 for (var property in source) {
60 destination[property] = source[property];
65 Object.extend(Object, {
66 inspect: function(object) {
68 if (object === undefined) return 'undefined';
69 if (object === null) return 'null';
70 return object.inspect ? object.inspect() : object.toString();
72 if (e instanceof RangeError) return '...';
77 keys: function(object) {
79 for (var property in object)
84 values: function(object) {
86 for (var property in object)
87 values.push(object[property]);
91 clone: function(object) {
92 return Object.extend({}, object);
96 Function.prototype.bind = function() {
97 var __method = this, args = $A(arguments), object = args.shift();
99 return __method.apply(object, args.concat($A(arguments)));
103 Function.prototype.bindAsEventListener = function(object) {
104 var __method = this, args = $A(arguments), object = args.shift();
105 return function(event) {
106 return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
110 Object.extend(Number.prototype, {
111 toColorPart: function() {
112 var digits = this.toString(16);
113 if (this < 16) return '0' + digits;
121 times: function(iterator) {
122 $R(0, this, true).each(iterator);
131 for (var i = 0, length = arguments.length; i < length; i++) {
132 var lambda = arguments[i];
134 returnValue = lambda();
143 /*--------------------------------------------------------------------------*/
145 var PeriodicalExecuter = Class.create();
146 PeriodicalExecuter.prototype = {
147 initialize: function(callback, frequency) {
148 this.callback = callback;
149 this.frequency = frequency;
150 this.currentlyExecuting = false;
152 this.registerCallback();
155 registerCallback: function() {
156 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
160 if (!this.timer) return;
161 clearInterval(this.timer);
165 onTimerEvent: function() {
166 if (!this.currentlyExecuting) {
168 this.currentlyExecuting = true;
171 this.currentlyExecuting = false;
176 String.interpret = function(value){
177 return value == null ? '' : String(value);
180 Object.extend(String.prototype, {
181 gsub: function(pattern, replacement) {
182 var result = '', source = this, match;
183 replacement = arguments.callee.prepareReplacement(replacement);
185 while (source.length > 0) {
186 if (match = source.match(pattern)) {
187 result += source.slice(0, match.index);
188 result += String.interpret(replacement(match));
189 source = source.slice(match.index + match[0].length);
191 result += source, source = '';
197 sub: function(pattern, replacement, count) {
198 replacement = this.gsub.prepareReplacement(replacement);
199 count = count === undefined ? 1 : count;
201 return this.gsub(pattern, function(match) {
202 if (--count < 0) return match[0];
203 return replacement(match);
207 scan: function(pattern, iterator) {
208 this.gsub(pattern, iterator);
212 truncate: function(length, truncation) {
213 length = length || 30;
214 truncation = truncation === undefined ? '...' : truncation;
215 return this.length > length ?
216 this.slice(0, length - truncation.length) + truncation : this;
220 return this.replace(/^\s+/, '').replace(/\s+$/, '');
223 stripTags: function() {
224 return this.replace(/<\/?[^>]+>/gi, '');
227 stripScripts: function() {
228 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
231 extractScripts: function() {
232 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
233 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
234 return (this.match(matchAll) || []).map(function(scriptTag) {
235 return (scriptTag.match(matchOne) || ['', ''])[1];
239 evalScripts: function() {
240 return this.extractScripts().map(function(script) { return eval(script) });
243 escapeHTML: function() {
244 var div = document.createElement('div');
245 var text = document.createTextNode(this);
246 div.appendChild(text);
247 return div.innerHTML;
250 unescapeHTML: function() {
251 var div = document.createElement('div');
252 div.innerHTML = this.stripTags();
253 return div.childNodes[0] ? (div.childNodes.length > 1 ?
254 $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
255 div.childNodes[0].nodeValue) : '';
258 toQueryParams: function(separator) {
259 var match = this.strip().match(/([^?#]*)(#.*)?$/);
260 if (!match) return {};
262 return match[1].split(separator || '&').inject({}, function(hash, pair) {
263 if ((pair = pair.split('='))[0]) {
264 var name = decodeURIComponent(pair[0]);
265 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
267 if (hash[name] !== undefined) {
268 if (hash[name].constructor != Array)
269 hash[name] = [hash[name]];
270 if (value) hash[name].push(value);
272 else hash[name] = value;
278 toArray: function() {
279 return this.split('');
283 return this.slice(0, this.length - 1) +
284 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
287 camelize: function() {
288 var parts = this.split('-'), len = parts.length;
289 if (len == 1) return parts[0];
291 var camelized = this.charAt(0) == '-'
292 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
295 for (var i = 1; i < len; i++)
296 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
301 capitalize: function(){
302 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
305 underscore: function() {
306 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
309 dasherize: function() {
310 return this.gsub(/_/,'-');
313 inspect: function(useDoubleQuotes) {
314 var escapedString = this.replace(/\\/g, '\\\\');
316 return '"' + escapedString.replace(/"/g, '\\"') + '"';
318 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
322 String.prototype.gsub.prepareReplacement = function(replacement) {
323 if (typeof replacement == 'function') return replacement;
324 var template = new Template(replacement);
325 return function(match) { return template.evaluate(match) };
328 String.prototype.parseQuery = String.prototype.toQueryParams;
330 var Template = Class.create();
331 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
332 Template.prototype = {
333 initialize: function(template, pattern) {
334 this.template = template.toString();
335 this.pattern = pattern || Template.Pattern;
338 evaluate: function(object) {
339 return this.template.gsub(this.pattern, function(match) {
340 var before = match[1];
341 if (before == '\\') return match[2];
342 return before + String.interpret(object[match[3]]);
347 var $break = new Object();
348 var $continue = new Object();
351 each: function(iterator) {
354 this._each(function(value) {
356 iterator(value, index++);
358 if (e != $continue) throw e;
362 if (e != $break) throw e;
367 eachSlice: function(number, iterator) {
368 var index = -number, slices = [], array = this.toArray();
369 while ((index += number) < array.length)
370 slices.push(array.slice(index, index+number));
371 return slices.map(iterator);
374 all: function(iterator) {
376 this.each(function(value, index) {
377 result = result && !!(iterator || Prototype.K)(value, index);
378 if (!result) throw $break;
383 any: function(iterator) {
385 this.each(function(value, index) {
386 if (result = !!(iterator || Prototype.K)(value, index))
392 collect: function(iterator) {
394 this.each(function(value, index) {
395 results.push((iterator || Prototype.K)(value, index));
400 detect: function(iterator) {
402 this.each(function(value, index) {
403 if (iterator(value, index)) {
411 findAll: function(iterator) {
413 this.each(function(value, index) {
414 if (iterator(value, index))
420 grep: function(pattern, iterator) {
422 this.each(function(value, index) {
423 var stringValue = value.toString();
424 if (stringValue.match(pattern))
425 results.push((iterator || Prototype.K)(value, index));
430 include: function(object) {
432 this.each(function(value) {
433 if (value == object) {
441 inGroupsOf: function(number, fillWith) {
442 fillWith = fillWith === undefined ? null : fillWith;
443 return this.eachSlice(number, function(slice) {
444 while(slice.length < number) slice.push(fillWith);
449 inject: function(memo, iterator) {
450 this.each(function(value, index) {
451 memo = iterator(memo, value, index);
456 invoke: function(method) {
457 var args = $A(arguments).slice(1);
458 return this.map(function(value) {
459 return value[method].apply(value, args);
463 max: function(iterator) {
465 this.each(function(value, index) {
466 value = (iterator || Prototype.K)(value, index);
467 if (result == undefined || value >= result)
473 min: function(iterator) {
475 this.each(function(value, index) {
476 value = (iterator || Prototype.K)(value, index);
477 if (result == undefined || value < result)
483 partition: function(iterator) {
484 var trues = [], falses = [];
485 this.each(function(value, index) {
486 ((iterator || Prototype.K)(value, index) ?
487 trues : falses).push(value);
489 return [trues, falses];
492 pluck: function(property) {
494 this.each(function(value, index) {
495 results.push(value[property]);
500 reject: function(iterator) {
502 this.each(function(value, index) {
503 if (!iterator(value, index))
509 sortBy: function(iterator) {
510 return this.map(function(value, index) {
511 return {value: value, criteria: iterator(value, index)};
512 }).sort(function(left, right) {
513 var a = left.criteria, b = right.criteria;
514 return a < b ? -1 : a > b ? 1 : 0;
518 toArray: function() {
523 var iterator = Prototype.K, args = $A(arguments);
524 if (typeof args.last() == 'function')
525 iterator = args.pop();
527 var collections = [this].concat(args).map($A);
528 return this.map(function(value, index) {
529 return iterator(collections.pluck(index));
534 return this.toArray().length;
537 inspect: function() {
538 return '#<Enumerable:' + this.toArray().inspect() + '>';
542 Object.extend(Enumerable, {
543 map: Enumerable.collect,
544 find: Enumerable.detect,
545 select: Enumerable.findAll,
546 member: Enumerable.include,
547 entries: Enumerable.toArray
549 var $A = Array.from = function(iterable) {
550 if (!iterable) return [];
551 if (iterable.toArray) {
552 return iterable.toArray();
555 for (var i = 0, length = iterable.length; i < length; i++)
556 results.push(iterable[i]);
561 Object.extend(Array.prototype, Enumerable);
563 if (!Array.prototype._reverse)
564 Array.prototype._reverse = Array.prototype.reverse;
566 Object.extend(Array.prototype, {
567 _each: function(iterator) {
568 for (var i = 0, length = this.length; i < length; i++)
582 return this[this.length - 1];
585 compact: function() {
586 return this.select(function(value) {
587 return value != null;
591 flatten: function() {
592 return this.inject([], function(array, value) {
593 return array.concat(value && value.constructor == Array ?
594 value.flatten() : [value]);
598 without: function() {
599 var values = $A(arguments);
600 return this.select(function(value) {
601 return !values.include(value);
605 indexOf: function(object) {
606 for (var i = 0, length = this.length; i < length; i++)
607 if (this[i] == object) return i;
611 reverse: function(inline) {
612 return (inline !== false ? this : this.toArray())._reverse();
616 return this.length > 1 ? this : this[0];
620 return this.inject([], function(array, value) {
621 return array.include(value) ? array : array.concat([value]);
626 return [].concat(this);
633 inspect: function() {
634 return '[' + this.map(Object.inspect).join(', ') + ']';
638 Array.prototype.toArray = Array.prototype.clone;
641 string = string.strip();
642 return string ? string.split(/\s+/) : [];
646 Array.prototype.concat = function(){
648 for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
649 for(var i = 0, length = arguments.length; i < length; i++) {
650 if(arguments[i].constructor == Array) {
651 for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
652 array.push(arguments[i][j]);
654 array.push(arguments[i]);
660 var Hash = function(obj) {
661 Object.extend(this, obj || {});
664 Object.extend(Hash, {
665 toQueryString: function(obj) {
668 this.prototype._each.call(obj, function(pair) {
669 if (!pair.key) return;
671 if (pair.value && pair.value.constructor == Array) {
672 var values = pair.value.compact();
673 if (values.length < 2) pair.value = values.reduce();
675 key = encodeURIComponent(pair.key);
676 values.each(function(value) {
677 value = value != undefined ? encodeURIComponent(value) : '';
678 parts.push(key + '=' + encodeURIComponent(value));
683 if (pair.value == undefined) pair[1] = '';
684 parts.push(pair.map(encodeURIComponent).join('='));
687 return parts.join('&');
691 Object.extend(Hash.prototype, Enumerable);
692 Object.extend(Hash.prototype, {
693 _each: function(iterator) {
694 for (var key in this) {
695 var value = this[key];
696 if (value && value == Hash.prototype[key]) continue;
698 var pair = [key, value];
706 return this.pluck('key');
710 return this.pluck('value');
713 merge: function(hash) {
714 return $H(hash).inject(this, function(mergedHash, pair) {
715 mergedHash[pair.key] = pair.value;
722 for(var i = 0, length = arguments.length; i < length; i++) {
723 var value = this[arguments[i]];
724 if (value !== undefined){
725 if (result === undefined) result = value;
727 if (result.constructor != Array) result = [result];
731 delete this[arguments[i]];
736 toQueryString: function() {
737 return Hash.toQueryString(this);
740 inspect: function() {
741 return '#<Hash:{' + this.map(function(pair) {
742 return pair.map(Object.inspect).join(': ');
743 }).join(', ') + '}>';
747 function $H(object) {
748 if (object && object.constructor == Hash) return object;
749 return new Hash(object);
751 ObjectRange = Class.create();
752 Object.extend(ObjectRange.prototype, Enumerable);
753 Object.extend(ObjectRange.prototype, {
754 initialize: function(start, end, exclusive) {
757 this.exclusive = exclusive;
760 _each: function(iterator) {
761 var value = this.start;
762 while (this.include(value)) {
764 value = value.succ();
768 include: function(value) {
769 if (value < this.start)
772 return value < this.end;
773 return value <= this.end;
777 var $R = function(start, end, exclusive) {
778 return new ObjectRange(start, end, exclusive);
782 getTransport: function() {
784 function() {return new XMLHttpRequest()},
785 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
786 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
790 activeRequestCount: 0
796 _each: function(iterator) {
797 this.responders._each(iterator);
800 register: function(responder) {
801 if (!this.include(responder))
802 this.responders.push(responder);
805 unregister: function(responder) {
806 this.responders = this.responders.without(responder);
809 dispatch: function(callback, request, transport, json) {
810 this.each(function(responder) {
811 if (typeof responder[callback] == 'function') {
813 responder[callback].apply(responder, [request, transport, json]);
820 Object.extend(Ajax.Responders, Enumerable);
822 Ajax.Responders.register({
823 onCreate: function() {
824 Ajax.activeRequestCount++;
826 onComplete: function() {
827 Ajax.activeRequestCount--;
831 Ajax.Base = function() {};
832 Ajax.Base.prototype = {
833 setOptions: function(options) {
837 contentType: 'application/x-www-form-urlencoded',
841 Object.extend(this.options, options || {});
843 this.options.method = this.options.method.toLowerCase();
844 if (typeof this.options.parameters == 'string')
845 this.options.parameters = this.options.parameters.toQueryParams();
849 Ajax.Request = Class.create();
850 Ajax.Request.Events =
851 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
853 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
856 initialize: function(url, options) {
857 this.transport = Ajax.getTransport();
858 this.setOptions(options);
862 request: function(url) {
864 this.method = this.options.method;
865 var params = this.options.parameters;
867 if (!['get', 'post'].include(this.method)) {
868 // simulate other verbs over post
869 params['_method'] = this.method;
870 this.method = 'post';
873 params = Hash.toQueryString(params);
874 if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
876 // when GET, append parameters to URL
877 if (this.method == 'get' && params)
878 this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
881 Ajax.Responders.dispatch('onCreate', this, this.transport);
883 this.transport.open(this.method.toUpperCase(), this.url,
884 this.options.asynchronous);
886 if (this.options.asynchronous)
887 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
889 this.transport.onreadystatechange = this.onStateChange.bind(this);
890 this.setRequestHeaders();
892 var body = this.method == 'post' ? (this.options.postBody || params) : null;
894 this.transport.send(body);
896 /* Force Firefox to handle ready state 4 for synchronous requests */
897 if (!this.options.asynchronous && this.transport.overrideMimeType)
898 this.onStateChange();
902 this.dispatchException(e);
906 onStateChange: function() {
907 var readyState = this.transport.readyState;
908 if (readyState > 1 && !((readyState == 4) && this._complete))
909 this.respondToReadyState(this.transport.readyState);
912 setRequestHeaders: function() {
914 'X-Requested-With': 'XMLHttpRequest',
915 'X-Prototype-Version': Prototype.Version,
916 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
919 if (this.method == 'post') {
920 headers['Content-type'] = this.options.contentType +
921 (this.options.encoding ? '; charset=' + this.options.encoding : '');
923 /* Force "Connection: close" for older Mozilla browsers to work
924 * around a bug where XMLHttpRequest sends an incorrect
925 * Content-length header. See Mozilla Bugzilla #246651.
927 if (this.transport.overrideMimeType &&
928 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
929 headers['Connection'] = 'close';
932 // user-defined headers
933 if (typeof this.options.requestHeaders == 'object') {
934 var extras = this.options.requestHeaders;
936 if (typeof extras.push == 'function')
937 for (var i = 0, length = extras.length; i < length; i += 2)
938 headers[extras[i]] = extras[i+1];
940 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
943 for (var name in headers)
944 this.transport.setRequestHeader(name, headers[name]);
947 success: function() {
948 return !this.transport.status
949 || (this.transport.status >= 200 && this.transport.status < 300);
952 respondToReadyState: function(readyState) {
953 var state = Ajax.Request.Events[readyState];
954 var transport = this.transport, json = this.evalJSON();
956 if (state == 'Complete') {
958 this._complete = true;
959 (this.options['on' + this.transport.status]
960 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
961 || Prototype.emptyFunction)(transport, json);
963 this.dispatchException(e);
966 if ((this.getHeader('Content-type') || 'text/javascript').strip().
967 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
972 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
973 Ajax.Responders.dispatch('on' + state, this, transport, json);
975 this.dispatchException(e);
978 if (state == 'Complete') {
979 // avoid memory leak in MSIE: clean up
980 this.transport.onreadystatechange = Prototype.emptyFunction;
984 getHeader: function(name) {
986 return this.transport.getResponseHeader(name);
987 } catch (e) { return null }
990 evalJSON: function() {
992 var json = this.getHeader('X-JSON');
993 return json ? eval('(' + json + ')') : null;
994 } catch (e) { return null }
997 evalResponse: function() {
999 return eval(this.transport.responseText);
1001 this.dispatchException(e);
1005 dispatchException: function(exception) {
1006 (this.options.onException || Prototype.emptyFunction)(this, exception);
1007 Ajax.Responders.dispatch('onException', this, exception);
1011 Ajax.Updater = Class.create();
1013 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1014 initialize: function(container, url, options) {
1016 success: (container.success || container),
1017 failure: (container.failure || (container.success ? null : container))
1020 this.transport = Ajax.getTransport();
1021 this.setOptions(options);
1023 var onComplete = this.options.onComplete || Prototype.emptyFunction;
1024 this.options.onComplete = (function(transport, param) {
1025 this.updateContent();
1026 onComplete(transport, param);
1032 updateContent: function() {
1033 var receiver = this.container[this.success() ? 'success' : 'failure'];
1034 var response = this.transport.responseText;
1036 if (!this.options.evalScripts) response = response.stripScripts();
1038 if (receiver = $(receiver)) {
1039 if (this.options.insertion)
1040 new this.options.insertion(receiver, response);
1042 receiver.update(response);
1045 if (this.success()) {
1046 if (this.onComplete)
1047 setTimeout(this.onComplete.bind(this), 10);
1052 Ajax.PeriodicalUpdater = Class.create();
1053 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1054 initialize: function(container, url, options) {
1055 this.setOptions(options);
1056 this.onComplete = this.options.onComplete;
1058 this.frequency = (this.options.frequency || 2);
1059 this.decay = (this.options.decay || 1);
1062 this.container = container;
1069 this.options.onComplete = this.updateComplete.bind(this);
1070 this.onTimerEvent();
1074 this.updater.options.onComplete = undefined;
1075 clearTimeout(this.timer);
1076 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1079 updateComplete: function(request) {
1080 if (this.options.decay) {
1081 this.decay = (request.responseText == this.lastText ?
1082 this.decay * this.options.decay : 1);
1084 this.lastText = request.responseText;
1086 this.timer = setTimeout(this.onTimerEvent.bind(this),
1087 this.decay * this.frequency * 1000);
1090 onTimerEvent: function() {
1091 this.updater = new Ajax.Updater(this.container, this.url, this.options);
1094 function $(element) {
1095 if (arguments.length > 1) {
1096 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1097 elements.push($(arguments[i]));
1100 if (typeof element == 'string')
1101 element = document.getElementById(element);
1102 return Element.extend(element);
1105 if (Prototype.BrowserFeatures.XPath) {
1106 document._getElementsByXPath = function(expression, parentElement) {
1108 var query = document.evaluate(expression, $(parentElement) || document,
1109 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1110 for (var i = 0, length = query.snapshotLength; i < length; i++)
1111 results.push(query.snapshotItem(i));
1116 document.getElementsByClassName = function(className, parentElement) {
1117 if (Prototype.BrowserFeatures.XPath) {
1118 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1119 return document._getElementsByXPath(q, parentElement);
1121 var children = ($(parentElement) || document.body).getElementsByTagName('*');
1122 var elements = [], child;
1123 for (var i = 0, length = children.length; i < length; i++) {
1124 child = children[i];
1125 if (Element.hasClassName(child, className))
1126 elements.push(Element.extend(child));
1132 /*--------------------------------------------------------------------------*/
1134 if (!window.Element)
1135 var Element = new Object();
1137 Element.extend = function(element) {
1138 if (!element || _nativeExtensions || element.nodeType == 3) return element;
1140 if (!element._extended && element.tagName && element != window) {
1141 var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
1143 if (element.tagName == 'FORM')
1144 Object.extend(methods, Form.Methods);
1145 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
1146 Object.extend(methods, Form.Element.Methods);
1148 Object.extend(methods, Element.Methods.Simulated);
1150 for (var property in methods) {
1151 var value = methods[property];
1152 if (typeof value == 'function' && !(property in element))
1153 element[property] = cache.findOrStore(value);
1157 element._extended = true;
1161 Element.extend.cache = {
1162 findOrStore: function(value) {
1163 return this[value] = this[value] || function() {
1164 return value.apply(null, [this].concat($A(arguments)));
1170 visible: function(element) {
1171 return $(element).style.display != 'none';
1174 toggle: function(element) {
1175 element = $(element);
1176 Element[Element.visible(element) ? 'hide' : 'show'](element);
1180 hide: function(element) {
1181 $(element).style.display = 'none';
1185 show: function(element) {
1186 $(element).style.display = '';
1190 remove: function(element) {
1191 element = $(element);
1192 element.parentNode.removeChild(element);
1196 update: function(element, html) {
1197 html = typeof html == 'undefined' ? '' : html.toString();
1198 $(element).innerHTML = html.stripScripts();
1199 setTimeout(function() {html.evalScripts()}, 10);
1203 replace: function(element, html) {
1204 element = $(element);
1205 html = typeof html == 'undefined' ? '' : html.toString();
1206 if (element.outerHTML) {
1207 element.outerHTML = html.stripScripts();
1209 var range = element.ownerDocument.createRange();
1210 range.selectNodeContents(element);
1211 element.parentNode.replaceChild(
1212 range.createContextualFragment(html.stripScripts()), element);
1214 setTimeout(function() {html.evalScripts()}, 10);
1218 inspect: function(element) {
1219 element = $(element);
1220 var result = '<' + element.tagName.toLowerCase();
1221 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1222 var property = pair.first(), attribute = pair.last();
1223 var value = (element[property] || '').toString();
1224 if (value) result += ' ' + attribute + '=' + value.inspect(true);
1226 return result + '>';
1229 recursivelyCollect: function(element, property) {
1230 element = $(element);
1232 while (element = element[property])
1233 if (element.nodeType == 1)
1234 elements.push(Element.extend(element));
1238 ancestors: function(element) {
1239 return $(element).recursivelyCollect('parentNode');
1242 descendants: function(element) {
1243 return $A($(element).getElementsByTagName('*'));
1246 immediateDescendants: function(element) {
1247 if (!(element = $(element).firstChild)) return [];
1248 while (element && element.nodeType != 1) element = element.nextSibling;
1249 if (element) return [element].concat($(element).nextSiblings());
1253 previousSiblings: function(element) {
1254 return $(element).recursivelyCollect('previousSibling');
1257 nextSiblings: function(element) {
1258 return $(element).recursivelyCollect('nextSibling');
1261 siblings: function(element) {
1262 element = $(element);
1263 return element.previousSiblings().reverse().concat(element.nextSiblings());
1266 match: function(element, selector) {
1267 if (typeof selector == 'string')
1268 selector = new Selector(selector);
1269 return selector.match($(element));
1272 up: function(element, expression, index) {
1273 return Selector.findElement($(element).ancestors(), expression, index);
1276 down: function(element, expression, index) {
1277 return Selector.findElement($(element).descendants(), expression, index);
1280 previous: function(element, expression, index) {
1281 return Selector.findElement($(element).previousSiblings(), expression, index);
1284 next: function(element, expression, index) {
1285 return Selector.findElement($(element).nextSiblings(), expression, index);
1288 getElementsBySelector: function() {
1289 var args = $A(arguments), element = $(args.shift());
1290 return Selector.findChildElements(element, args);
1293 getElementsByClassName: function(element, className) {
1294 return document.getElementsByClassName(className, element);
1297 readAttribute: function(element, name) {
1298 element = $(element);
1299 if (document.all && !window.opera) {
1300 var t = Element._attributeTranslations;
1301 if (t.values[name]) return t.values[name](element, name);
1302 if (t.names[name]) name = t.names[name];
1303 var attribute = element.attributes[name];
1304 if(attribute) return attribute.nodeValue;
1306 return element.getAttribute(name);
1309 getHeight: function(element) {
1310 return $(element).getDimensions().height;
1313 getWidth: function(element) {
1314 return $(element).getDimensions().width;
1317 classNames: function(element) {
1318 return new Element.ClassNames(element);
1321 hasClassName: function(element, className) {
1322 if (!(element = $(element))) return;
1323 var elementClassName = element.className;
1324 if (elementClassName.length == 0) return false;
1325 if (elementClassName == className ||
1326 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1331 addClassName: function(element, className) {
1332 if (!(element = $(element))) return;
1333 Element.classNames(element).add(className);
1337 removeClassName: function(element, className) {
1338 if (!(element = $(element))) return;
1339 Element.classNames(element).remove(className);
1343 toggleClassName: function(element, className) {
1344 if (!(element = $(element))) return;
1345 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1349 observe: function() {
1350 Event.observe.apply(Event, arguments);
1351 return $A(arguments).first();
1354 stopObserving: function() {
1355 Event.stopObserving.apply(Event, arguments);
1356 return $A(arguments).first();
1359 // removes whitespace-only text node children
1360 cleanWhitespace: function(element) {
1361 element = $(element);
1362 var node = element.firstChild;
1364 var nextNode = node.nextSibling;
1365 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1366 element.removeChild(node);
1372 empty: function(element) {
1373 return $(element).innerHTML.match(/^\s*$/);
1376 descendantOf: function(element, ancestor) {
1377 element = $(element), ancestor = $(ancestor);
1378 while (element = element.parentNode)
1379 if (element == ancestor) return true;
1383 scrollTo: function(element) {
1384 element = $(element);
1385 var pos = Position.cumulativeOffset(element);
1386 window.scrollTo(pos[0], pos[1]);
1390 getStyle: function(element, style) {
1391 element = $(element);
1392 if (['float','cssFloat'].include(style))
1393 style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
1394 style = style.camelize();
1395 var value = element.style[style];
1397 if (document.defaultView && document.defaultView.getComputedStyle) {
1398 var css = document.defaultView.getComputedStyle(element, null);
1399 value = css ? css[style] : null;
1400 } else if (element.currentStyle) {
1401 value = element.currentStyle[style];
1405 if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
1406 value = element['offset'+style.capitalize()] + 'px';
1408 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
1409 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
1410 if(style == 'opacity') {
1411 if(value) return parseFloat(value);
1412 if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1413 if(value[1]) return parseFloat(value[1]) / 100;
1416 return value == 'auto' ? null : value;
1419 setStyle: function(element, style) {
1420 element = $(element);
1421 for (var name in style) {
1422 var value = style[name];
1423 if(name == 'opacity') {
1425 value = (/Gecko/.test(navigator.userAgent) &&
1426 !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
1427 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1428 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1429 } else if(value == '') {
1430 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1431 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1433 if(value < 0.00001) value = 0;
1434 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1435 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
1436 'alpha(opacity='+value*100+')';
1438 } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
1439 element.style[name.camelize()] = value;
1444 getDimensions: function(element) {
1445 element = $(element);
1446 var display = $(element).getStyle('display');
1447 if (display != 'none' && display != null) // Safari bug
1448 return {width: element.offsetWidth, height: element.offsetHeight};
1450 // All *Width and *Height properties give 0 on elements with display none,
1451 // so enable the element temporarily
1452 var els = element.style;
1453 var originalVisibility = els.visibility;
1454 var originalPosition = els.position;
1455 var originalDisplay = els.display;
1456 els.visibility = 'hidden';
1457 els.position = 'absolute';
1458 els.display = 'block';
1459 var originalWidth = element.clientWidth;
1460 var originalHeight = element.clientHeight;
1461 els.display = originalDisplay;
1462 els.position = originalPosition;
1463 els.visibility = originalVisibility;
1464 return {width: originalWidth, height: originalHeight};
1467 makePositioned: function(element) {
1468 element = $(element);
1469 var pos = Element.getStyle(element, 'position');
1470 if (pos == 'static' || !pos) {
1471 element._madePositioned = true;
1472 element.style.position = 'relative';
1473 // Opera returns the offset relative to the positioning context, when an
1474 // element is position relative but top and left have not been defined
1476 element.style.top = 0;
1477 element.style.left = 0;
1483 undoPositioned: function(element) {
1484 element = $(element);
1485 if (element._madePositioned) {
1486 element._madePositioned = undefined;
1487 element.style.position =
1489 element.style.left =
1490 element.style.bottom =
1491 element.style.right = '';
1496 makeClipping: function(element) {
1497 element = $(element);
1498 if (element._overflow) return element;
1499 element._overflow = element.style.overflow || 'auto';
1500 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1501 element.style.overflow = 'hidden';
1505 undoClipping: function(element) {
1506 element = $(element);
1507 if (!element._overflow) return element;
1508 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1509 element._overflow = null;
1514 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
1516 Element._attributeTranslations = {};
1518 Element._attributeTranslations.names = {
1522 datetime: "dateTime",
1523 accesskey: "accessKey",
1524 tabindex: "tabIndex",
1526 maxlength: "maxLength",
1527 readonly: "readOnly",
1528 longdesc: "longDesc"
1531 Element._attributeTranslations.values = {
1532 _getAttr: function(element, attribute) {
1533 return element.getAttribute(attribute, 2);
1536 _flag: function(element, attribute) {
1537 return $(element).hasAttribute(attribute) ? attribute : null;
1540 style: function(element) {
1541 return element.style.cssText.toLowerCase();
1544 title: function(element) {
1545 var node = element.getAttributeNode('title');
1546 return node.specified ? node.nodeValue : null;
1550 Object.extend(Element._attributeTranslations.values, {
1551 href: Element._attributeTranslations.values._getAttr,
1552 src: Element._attributeTranslations.values._getAttr,
1553 disabled: Element._attributeTranslations.values._flag,
1554 checked: Element._attributeTranslations.values._flag,
1555 readonly: Element._attributeTranslations.values._flag,
1556 multiple: Element._attributeTranslations.values._flag
1559 Element.Methods.Simulated = {
1560 hasAttribute: function(element, attribute) {
1561 var t = Element._attributeTranslations;
1562 attribute = t.names[attribute] || attribute;
1563 return $(element).getAttributeNode(attribute).specified;
1567 // IE is missing .innerHTML support for TABLE-related elements
1568 if (document.all && !window.opera){
1569 Element.Methods.update = function(element, html) {
1570 element = $(element);
1571 html = typeof html == 'undefined' ? '' : html.toString();
1572 var tagName = element.tagName.toUpperCase();
1573 if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1574 var div = document.createElement('div');
1578 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1582 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1586 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1589 $A(element.childNodes).each(function(node){
1590 element.removeChild(node)
1592 depth.times(function(){ div = div.firstChild });
1594 $A(div.childNodes).each(
1595 function(node){ element.appendChild(node) });
1597 element.innerHTML = html.stripScripts();
1599 setTimeout(function() {html.evalScripts()}, 10);
1604 Object.extend(Element, Element.Methods);
1606 var _nativeExtensions = false;
1608 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1609 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
1610 var className = 'HTML' + tag + 'Element';
1611 if(window[className]) return;
1612 var klass = window[className] = {};
1613 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
1616 Element.addMethods = function(methods) {
1617 Object.extend(Element.Methods, methods || {});
1619 function copy(methods, destination, onlyIfAbsent) {
1620 onlyIfAbsent = onlyIfAbsent || false;
1621 var cache = Element.extend.cache;
1622 for (var property in methods) {
1623 var value = methods[property];
1624 if (!onlyIfAbsent || !(property in destination))
1625 destination[property] = cache.findOrStore(value);
1629 if (typeof HTMLElement != 'undefined') {
1630 copy(Element.Methods, HTMLElement.prototype);
1631 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1632 copy(Form.Methods, HTMLFormElement.prototype);
1633 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
1634 copy(Form.Element.Methods, klass.prototype);
1636 _nativeExtensions = true;
1640 var Toggle = new Object();
1641 Toggle.display = Element.toggle;
1643 /*--------------------------------------------------------------------------*/
1645 Abstract.Insertion = function(adjacency) {
1646 this.adjacency = adjacency;
1649 Abstract.Insertion.prototype = {
1650 initialize: function(element, content) {
1651 this.element = $(element);
1652 this.content = content.stripScripts();
1654 if (this.adjacency && this.element.insertAdjacentHTML) {
1656 this.element.insertAdjacentHTML(this.adjacency, this.content);
1658 var tagName = this.element.tagName.toUpperCase();
1659 if (['TBODY', 'TR'].include(tagName)) {
1660 this.insertContent(this.contentFromAnonymousTable());
1666 this.range = this.element.ownerDocument.createRange();
1667 if (this.initializeRange) this.initializeRange();
1668 this.insertContent([this.range.createContextualFragment(this.content)]);
1671 setTimeout(function() {content.evalScripts()}, 10);
1674 contentFromAnonymousTable: function() {
1675 var div = document.createElement('div');
1676 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1677 return $A(div.childNodes[0].childNodes[0].childNodes);
1681 var Insertion = new Object();
1683 Insertion.Before = Class.create();
1684 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1685 initializeRange: function() {
1686 this.range.setStartBefore(this.element);
1689 insertContent: function(fragments) {
1690 fragments.each((function(fragment) {
1691 this.element.parentNode.insertBefore(fragment, this.element);
1696 Insertion.Top = Class.create();
1697 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1698 initializeRange: function() {
1699 this.range.selectNodeContents(this.element);
1700 this.range.collapse(true);
1703 insertContent: function(fragments) {
1704 fragments.reverse(false).each((function(fragment) {
1705 this.element.insertBefore(fragment, this.element.firstChild);
1710 Insertion.Bottom = Class.create();
1711 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1712 initializeRange: function() {
1713 this.range.selectNodeContents(this.element);
1714 this.range.collapse(this.element);
1717 insertContent: function(fragments) {
1718 fragments.each((function(fragment) {
1719 this.element.appendChild(fragment);
1724 Insertion.After = Class.create();
1725 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1726 initializeRange: function() {
1727 this.range.setStartAfter(this.element);
1730 insertContent: function(fragments) {
1731 fragments.each((function(fragment) {
1732 this.element.parentNode.insertBefore(fragment,
1733 this.element.nextSibling);
1738 /*--------------------------------------------------------------------------*/
1740 Element.ClassNames = Class.create();
1741 Element.ClassNames.prototype = {
1742 initialize: function(element) {
1743 this.element = $(element);
1746 _each: function(iterator) {
1747 this.element.className.split(/\s+/).select(function(name) {
1748 return name.length > 0;
1752 set: function(className) {
1753 this.element.className = className;
1756 add: function(classNameToAdd) {
1757 if (this.include(classNameToAdd)) return;
1758 this.set($A(this).concat(classNameToAdd).join(' '));
1761 remove: function(classNameToRemove) {
1762 if (!this.include(classNameToRemove)) return;
1763 this.set($A(this).without(classNameToRemove).join(' '));
1766 toString: function() {
1767 return $A(this).join(' ');
1771 Object.extend(Element.ClassNames.prototype, Enumerable);
1772 var Selector = Class.create();
1773 Selector.prototype = {
1774 initialize: function(expression) {
1775 this.params = {classNames: []};
1776 this.expression = expression.toString().strip();
1777 this.parseExpression();
1778 this.compileMatcher();
1781 parseExpression: function() {
1782 function abort(message) { throw 'Parse error in selector: ' + message; }
1784 if (this.expression == '') abort('empty expression');
1786 var params = this.params, expr = this.expression, match, modifier, clause, rest;
1787 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
1788 params.attributes = params.attributes || [];
1789 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
1793 if (expr == '*') return this.params.wildcard = true;
1795 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
1796 modifier = match[1], clause = match[2], rest = match[3];
1798 case '#': params.id = clause; break;
1799 case '.': params.classNames.push(clause); break;
1801 case undefined: params.tagName = clause.toUpperCase(); break;
1802 default: abort(expr.inspect());
1807 if (expr.length > 0) abort(expr.inspect());
1810 buildMatchExpression: function() {
1811 var params = this.params, conditions = [], clause;
1813 if (params.wildcard)
1814 conditions.push('true');
1815 if (clause = params.id)
1816 conditions.push('element.readAttribute("id") == ' + clause.inspect());
1817 if (clause = params.tagName)
1818 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
1819 if ((clause = params.classNames).length > 0)
1820 for (var i = 0, length = clause.length; i < length; i++)
1821 conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
1822 if (clause = params.attributes) {
1823 clause.each(function(attribute) {
1824 var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
1825 var splitValueBy = function(delimiter) {
1826 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
1829 switch (attribute.operator) {
1830 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
1831 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
1832 case '|=': conditions.push(
1833 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
1835 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
1837 case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
1838 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
1843 return conditions.join(' && ');
1846 compileMatcher: function() {
1847 this.match = new Function('element', 'if (!element.tagName) return false; \
1848 element = $(element); \
1849 return ' + this.buildMatchExpression());
1852 findElements: function(scope) {
1855 if (element = $(this.params.id))
1856 if (this.match(element))
1857 if (!scope || Element.childOf(element, scope))
1860 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
1863 for (var i = 0, length = scope.length; i < length; i++)
1864 if (this.match(element = scope[i]))
1865 results.push(Element.extend(element));
1870 toString: function() {
1871 return this.expression;
1875 Object.extend(Selector, {
1876 matchElements: function(elements, expression) {
1877 var selector = new Selector(expression);
1878 return elements.select(selector.match.bind(selector)).map(Element.extend);
1881 findElement: function(elements, expression, index) {
1882 if (typeof expression == 'number') index = expression, expression = false;
1883 return Selector.matchElements(elements, expression || '*')[index || 0];
1886 findChildElements: function(element, expressions) {
1887 return expressions.map(function(expression) {
1888 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
1889 var selector = new Selector(expr);
1890 return results.inject([], function(elements, result) {
1891 return elements.concat(selector.findElements(result || element));
1899 return Selector.findChildElements(document, $A(arguments));
1902 reset: function(form) {
1907 serializeElements: function(elements, getHash) {
1908 var data = elements.inject({}, function(result, element) {
1909 if (!element.disabled && element.name) {
1910 var key = element.name, value = $(element).getValue();
1911 if (value != undefined) {
1913 if (result[key].constructor != Array) result[key] = [result[key]];
1914 result[key].push(value);
1916 else result[key] = value;
1922 return getHash ? data : Hash.toQueryString(data);
1927 serialize: function(form, getHash) {
1928 return Form.serializeElements(Form.getElements(form), getHash);
1931 getElements: function(form) {
1932 return $A($(form).getElementsByTagName('*')).inject([],
1933 function(elements, child) {
1934 if (Form.Element.Serializers[child.tagName.toLowerCase()])
1935 elements.push(Element.extend(child));
1941 getInputs: function(form, typeName, name) {
1943 var inputs = form.getElementsByTagName('input');
1945 if (!typeName && !name) return $A(inputs).map(Element.extend);
1947 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
1948 var input = inputs[i];
1949 if ((typeName && input.type != typeName) || (name && input.name != name))
1951 matchingInputs.push(Element.extend(input));
1954 return matchingInputs;
1957 disable: function(form) {
1959 form.getElements().each(function(element) {
1961 element.disabled = 'true';
1966 enable: function(form) {
1968 form.getElements().each(function(element) {
1969 element.disabled = '';
1974 findFirstElement: function(form) {
1975 return $(form).getElements().find(function(element) {
1976 return element.type != 'hidden' && !element.disabled &&
1977 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1981 focusFirstElement: function(form) {
1983 form.findFirstElement().activate();
1988 Object.extend(Form, Form.Methods);
1990 /*--------------------------------------------------------------------------*/
1993 focus: function(element) {
1998 select: function(element) {
1999 $(element).select();
2004 Form.Element.Methods = {
2005 serialize: function(element) {
2006 element = $(element);
2007 if (!element.disabled && element.name) {
2008 var value = element.getValue();
2009 if (value != undefined) {
2011 pair[element.name] = value;
2012 return Hash.toQueryString(pair);
2018 getValue: function(element) {
2019 element = $(element);
2020 var method = element.tagName.toLowerCase();
2021 return Form.Element.Serializers[method](element);
2024 clear: function(element) {
2025 $(element).value = '';
2029 present: function(element) {
2030 return $(element).value != '';
2033 activate: function(element) {
2034 element = $(element);
2036 if (element.select && ( element.tagName.toLowerCase() != 'input' ||
2037 !['button', 'reset', 'submit'].include(element.type) ) )
2042 disable: function(element) {
2043 element = $(element);
2044 element.disabled = true;
2048 enable: function(element) {
2049 element = $(element);
2051 element.disabled = false;
2056 Object.extend(Form.Element, Form.Element.Methods);
2057 var Field = Form.Element;
2058 var $F = Form.Element.getValue;
2060 /*--------------------------------------------------------------------------*/
2062 Form.Element.Serializers = {
2063 input: function(element) {
2064 switch (element.type.toLowerCase()) {
2067 return Form.Element.Serializers.inputSelector(element);
2069 return Form.Element.Serializers.textarea(element);
2073 inputSelector: function(element) {
2074 return element.checked ? element.value : null;
2077 textarea: function(element) {
2078 return element.value;
2081 select: function(element) {
2082 return this[element.type == 'select-one' ?
2083 'selectOne' : 'selectMany'](element);
2086 selectOne: function(element) {
2087 var index = element.selectedIndex;
2088 return index >= 0 ? this.optionValue(element.options[index]) : null;
2091 selectMany: function(element) {
2092 var values, length = element.length;
2093 if (!length) return null;
2095 for (var i = 0, values = []; i < length; i++) {
2096 var opt = element.options[i];
2097 if (opt.selected) values.push(this.optionValue(opt));
2102 optionValue: function(opt) {
2103 // extend element because hasAttribute may not be native
2104 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2108 /*--------------------------------------------------------------------------*/
2110 Abstract.TimedObserver = function() {}
2111 Abstract.TimedObserver.prototype = {
2112 initialize: function(element, frequency, callback) {
2113 this.frequency = frequency;
2114 this.element = $(element);
2115 this.callback = callback;
2117 this.lastValue = this.getValue();
2118 this.registerCallback();
2121 registerCallback: function() {
2122 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
2125 onTimerEvent: function() {
2126 var value = this.getValue();
2127 var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2128 ? this.lastValue != value : String(this.lastValue) != String(value));
2130 this.callback(this.element, value);
2131 this.lastValue = value;
2136 Form.Element.Observer = Class.create();
2137 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2138 getValue: function() {
2139 return Form.Element.getValue(this.element);
2143 Form.Observer = Class.create();
2144 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2145 getValue: function() {
2146 return Form.serialize(this.element);
2150 /*--------------------------------------------------------------------------*/
2152 Abstract.EventObserver = function() {}
2153 Abstract.EventObserver.prototype = {
2154 initialize: function(element, callback) {
2155 this.element = $(element);
2156 this.callback = callback;
2158 this.lastValue = this.getValue();
2159 if (this.element.tagName.toLowerCase() == 'form')
2160 this.registerFormCallbacks();
2162 this.registerCallback(this.element);
2165 onElementEvent: function() {
2166 var value = this.getValue();
2167 if (this.lastValue != value) {
2168 this.callback(this.element, value);
2169 this.lastValue = value;
2173 registerFormCallbacks: function() {
2174 Form.getElements(this.element).each(this.registerCallback.bind(this));
2177 registerCallback: function(element) {
2179 switch (element.type.toLowerCase()) {
2182 Event.observe(element, 'click', this.onElementEvent.bind(this));
2185 Event.observe(element, 'change', this.onElementEvent.bind(this));
2192 Form.Element.EventObserver = Class.create();
2193 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2194 getValue: function() {
2195 return Form.Element.getValue(this.element);
2199 Form.EventObserver = Class.create();
2200 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2201 getValue: function() {
2202 return Form.serialize(this.element);
2205 if (!window.Event) {
2206 var Event = new Object();
2209 Object.extend(Event, {
2224 element: function(event) {
2225 return event.target || event.srcElement;
2228 isLeftClick: function(event) {
2229 return (((event.which) && (event.which == 1)) ||
2230 ((event.button) && (event.button == 1)));
2233 pointerX: function(event) {
2234 return event.pageX || (event.clientX +
2235 (document.documentElement.scrollLeft || document.body.scrollLeft));
2238 pointerY: function(event) {
2239 return event.pageY || (event.clientY +
2240 (document.documentElement.scrollTop || document.body.scrollTop));
2243 stop: function(event) {
2244 if (event.preventDefault) {
2245 event.preventDefault();
2246 event.stopPropagation();
2248 event.returnValue = false;
2249 event.cancelBubble = true;
2253 // find the first node with the given tagName, starting from the
2254 // node the event was triggered on; traverses the DOM upwards
2255 findElement: function(event, tagName) {
2256 var element = Event.element(event);
2257 while (element.parentNode && (!element.tagName ||
2258 (element.tagName.toUpperCase() != tagName.toUpperCase())))
2259 element = element.parentNode;
2265 _observeAndCache: function(element, name, observer, useCapture) {
2266 if (!this.observers) this.observers = [];
2267 if (element.addEventListener) {
2268 this.observers.push([element, name, observer, useCapture]);
2269 element.addEventListener(name, observer, useCapture);
2270 } else if (element.attachEvent) {
2271 this.observers.push([element, name, observer, useCapture]);
2272 element.attachEvent('on' + name, observer);
2276 unloadCache: function() {
2277 if (!Event.observers) return;
2278 for (var i = 0, length = Event.observers.length; i < length; i++) {
2279 Event.stopObserving.apply(this, Event.observers[i]);
2280 Event.observers[i][0] = null;
2282 Event.observers = false;
2285 observe: function(element, name, observer, useCapture) {
2286 element = $(element);
2287 useCapture = useCapture || false;
2289 if (name == 'keypress' &&
2290 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2291 || element.attachEvent))
2294 Event._observeAndCache(element, name, observer, useCapture);
2297 stopObserving: function(element, name, observer, useCapture) {
2298 element = $(element);
2299 useCapture = useCapture || false;
2301 if (name == 'keypress' &&
2302 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2303 || element.detachEvent))
2306 if (element.removeEventListener) {
2307 element.removeEventListener(name, observer, useCapture);
2308 } else if (element.detachEvent) {
2310 element.detachEvent('on' + name, observer);
2316 /* prevent memory leaks in IE */
2317 if (navigator.appVersion.match(/\bMSIE\b/))
2318 Event.observe(window, 'unload', Event.unloadCache, false);
2320 // set to true if needed, warning: firefox performance problems
2321 // NOT neeeded for page scrolling, only if draggable contained in
2322 // scrollable elements
2323 includeScrollOffsets: false,
2325 // must be called before calling withinIncludingScrolloffset, every time the
2327 prepare: function() {
2328 this.deltaX = window.pageXOffset
2329 || document.documentElement.scrollLeft
2330 || document.body.scrollLeft
2332 this.deltaY = window.pageYOffset
2333 || document.documentElement.scrollTop
2334 || document.body.scrollTop
2338 realOffset: function(element) {
2339 var valueT = 0, valueL = 0;
2341 valueT += element.scrollTop || 0;
2342 valueL += element.scrollLeft || 0;
2343 element = element.parentNode;
2345 return [valueL, valueT];
2348 cumulativeOffset: function(element) {
2349 var valueT = 0, valueL = 0;
2351 valueT += element.offsetTop || 0;
2352 valueL += element.offsetLeft || 0;
2353 element = element.offsetParent;
2355 return [valueL, valueT];
2358 positionedOffset: function(element) {
2359 var valueT = 0, valueL = 0;
2361 valueT += element.offsetTop || 0;
2362 valueL += element.offsetLeft || 0;
2363 element = element.offsetParent;
2365 if(element.tagName=='BODY') break;
2366 var p = Element.getStyle(element, 'position');
2367 if (p == 'relative' || p == 'absolute') break;
2370 return [valueL, valueT];
2373 offsetParent: function(element) {
2374 if (element.offsetParent) return element.offsetParent;
2375 if (element == document.body) return element;
2377 while ((element = element.parentNode) && element != document.body)
2378 if (Element.getStyle(element, 'position') != 'static')
2381 return document.body;
2384 // caches x/y coordinate pair to use with overlap
2385 within: function(element, x, y) {
2386 if (this.includeScrollOffsets)
2387 return this.withinIncludingScrolloffsets(element, x, y);
2390 this.offset = this.cumulativeOffset(element);
2392 return (y >= this.offset[1] &&
2393 y < this.offset[1] + element.offsetHeight &&
2394 x >= this.offset[0] &&
2395 x < this.offset[0] + element.offsetWidth);
2398 withinIncludingScrolloffsets: function(element, x, y) {
2399 var offsetcache = this.realOffset(element);
2401 this.xcomp = x + offsetcache[0] - this.deltaX;
2402 this.ycomp = y + offsetcache[1] - this.deltaY;
2403 this.offset = this.cumulativeOffset(element);
2405 return (this.ycomp >= this.offset[1] &&
2406 this.ycomp < this.offset[1] + element.offsetHeight &&
2407 this.xcomp >= this.offset[0] &&
2408 this.xcomp < this.offset[0] + element.offsetWidth);
2411 // within must be called directly before
2412 overlap: function(mode, element) {
2413 if (!mode) return 0;
2414 if (mode == 'vertical')
2415 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
2416 element.offsetHeight;
2417 if (mode == 'horizontal')
2418 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
2419 element.offsetWidth;
2422 page: function(forElement) {
2423 var valueT = 0, valueL = 0;
2425 var element = forElement;
2427 valueT += element.offsetTop || 0;
2428 valueL += element.offsetLeft || 0;
2431 if (element.offsetParent==document.body)
2432 if (Element.getStyle(element,'position')=='absolute') break;
2434 } while (element = element.offsetParent);
2436 element = forElement;
2438 if (!window.opera || element.tagName=='BODY') {
2439 valueT -= element.scrollTop || 0;
2440 valueL -= element.scrollLeft || 0;
2442 } while (element = element.parentNode);
2444 return [valueL, valueT];
2447 clone: function(source, target) {
2448 var options = Object.extend({
2455 }, arguments[2] || {})
2457 // find page position of source
2459 var p = Position.page(source);
2461 // find coordinate system to use
2465 // delta [0,0] will do fine with position: fixed elements,
2466 // position:absolute needs offsetParent deltas
2467 if (Element.getStyle(target,'position') == 'absolute') {
2468 parent = Position.offsetParent(target);
2469 delta = Position.page(parent);
2472 // correct by body offsets (fixes Safari)
2473 if (parent == document.body) {
2474 delta[0] -= document.body.offsetLeft;
2475 delta[1] -= document.body.offsetTop;
2479 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2480 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2481 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
2482 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
2485 absolutize: function(element) {
2486 element = $(element);
2487 if (element.style.position == 'absolute') return;
2490 var offsets = Position.positionedOffset(element);
2491 var top = offsets[1];
2492 var left = offsets[0];
2493 var width = element.clientWidth;
2494 var height = element.clientHeight;
2496 element._originalLeft = left - parseFloat(element.style.left || 0);
2497 element._originalTop = top - parseFloat(element.style.top || 0);
2498 element._originalWidth = element.style.width;
2499 element._originalHeight = element.style.height;
2501 element.style.position = 'absolute';
2502 element.style.top = top + 'px';
2503 element.style.left = left + 'px';
2504 element.style.width = width + 'px';
2505 element.style.height = height + 'px';
2508 relativize: function(element) {
2509 element = $(element);
2510 if (element.style.position == 'relative') return;
2513 element.style.position = 'relative';
2514 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2515 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2517 element.style.top = top + 'px';
2518 element.style.left = left + 'px';
2519 element.style.height = element._originalHeight;
2520 element.style.width = element._originalWidth;
2524 // Safari returns margins on body which is incorrect if the child is absolutely
2525 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2526 // KHTML/WebKit only.
2527 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
2528 Position.cumulativeOffset = function(element) {
2529 var valueT = 0, valueL = 0;
2531 valueT += element.offsetTop || 0;
2532 valueL += element.offsetLeft || 0;
2533 if (element.offsetParent == document.body)
2534 if (Element.getStyle(element, 'position') == 'absolute') break;
2536 element = element.offsetParent;
2539 return [valueL, valueT];
2543 Element.addMethods();
2546 // ------------------------------------------------------------------------
2547 // ------------------------------------------------------------------------
2549 // The rest of this file is the actual ray tracer written by Adam
2550 // Burmister. It's a concatenation of the following files:
2557 // flog/material/basematerial.js
2558 // flog/material/solid.js
2559 // flog/material/chessboard.js
2560 // flog/shape/baseshape.js
2561 // flog/shape/sphere.js
2562 // flog/shape/plane.js
2563 // flog/intersectioninfo.js
2565 // flog/background.js
2569 /* Fake a Flog.* namespace */
2570 if(typeof(Flog) == 'undefined') var Flog = {};
2571 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2573 Flog.RayTracer.Color = Class.create();
2575 Flog.RayTracer.Color.prototype = {
2580 initialize : function(r, g, b) {
2590 add : function(c1, c2){
2591 var result = new Flog.RayTracer.Color(0,0,0);
2593 result.red = c1.red + c2.red;
2594 result.green = c1.green + c2.green;
2595 result.blue = c1.blue + c2.blue;
2600 addScalar: function(c1, s){
2601 var result = new Flog.RayTracer.Color(0,0,0);
2603 result.red = c1.red + s;
2604 result.green = c1.green + s;
2605 result.blue = c1.blue + s;
2612 subtract: function(c1, c2){
2613 var result = new Flog.RayTracer.Color(0,0,0);
2615 result.red = c1.red - c2.red;
2616 result.green = c1.green - c2.green;
2617 result.blue = c1.blue - c2.blue;
2622 multiply : function(c1, c2) {
2623 var result = new Flog.RayTracer.Color(0,0,0);
2625 result.red = c1.red * c2.red;
2626 result.green = c1.green * c2.green;
2627 result.blue = c1.blue * c2.blue;
2632 multiplyScalar : function(c1, f) {
2633 var result = new Flog.RayTracer.Color(0,0,0);
2635 result.red = c1.red * f;
2636 result.green = c1.green * f;
2637 result.blue = c1.blue * f;
2642 divideFactor : function(c1, f) {
2643 var result = new Flog.RayTracer.Color(0,0,0);
2645 result.red = c1.red / f;
2646 result.green = c1.green / f;
2647 result.blue = c1.blue / f;
2653 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0;
2654 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0;
2655 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0;
2658 distance : function(color) {
2659 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue);
2663 blend: function(c1, c2, w){
2664 var result = new Flog.RayTracer.Color(0,0,0);
2665 result = Flog.RayTracer.Color.prototype.add(
2666 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w),
2667 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w)
2672 toString : function () {
2673 var r = Math.floor(this.red*255);
2674 var g = Math.floor(this.green*255);
2675 var b = Math.floor(this.blue*255);
2677 return "rgb("+ r +","+ g +","+ b +")";
2680 /* Fake a Flog.* namespace */
2681 if(typeof(Flog) == 'undefined') var Flog = {};
2682 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2684 Flog.RayTracer.Light = Class.create();
2686 Flog.RayTracer.Light.prototype = {
2691 initialize : function(pos, color, intensity) {
2692 this.position = pos;
2694 this.intensity = (intensity ? intensity : 10.0);
2697 getIntensity: function(distance){
2698 if(distance >= intensity) return 0;
2700 return Math.pow((intensity - distance) / strength, 0.2);
2703 toString : function () {
2704 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
2707 /* Fake a Flog.* namespace */
2708 if(typeof(Flog) == 'undefined') var Flog = {};
2709 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2711 Flog.RayTracer.Vector = Class.create();
2713 Flog.RayTracer.Vector.prototype = {
2718 initialize : function(x, y, z) {
2719 this.x = (x ? x : 0);
2720 this.y = (y ? y : 0);
2721 this.z = (z ? z : 0);
2724 copy: function(vector){
2730 normalize : function() {
2731 var m = this.magnitude();
2732 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
2735 magnitude : function() {
2736 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
2739 cross : function(w) {
2740 return new Flog.RayTracer.Vector(
2741 -this.z * w.y + this.y * w.z,
2742 this.z * w.x - this.x * w.z,
2743 -this.y * w.x + this.x * w.y);
2747 return this.x * w.x + this.y * w.y + this.z * w.z;
2750 add : function(v, w) {
2751 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
2754 subtract : function(v, w) {
2755 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
2756 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
2759 multiplyVector : function(v, w) {
2760 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
2763 multiplyScalar : function(v, w) {
2764 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
2767 toString : function () {
2768 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
2771 /* Fake a Flog.* namespace */
2772 if(typeof(Flog) == 'undefined') var Flog = {};
2773 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2775 Flog.RayTracer.Ray = Class.create();
2777 Flog.RayTracer.Ray.prototype = {
2780 initialize : function(pos, dir) {
2781 this.position = pos;
2782 this.direction = dir;
2785 toString : function () {
2786 return 'Ray [' + this.position + ',' + this.direction + ']';
2789 /* Fake a Flog.* namespace */
2790 if(typeof(Flog) == 'undefined') var Flog = {};
2791 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2793 Flog.RayTracer.Scene = Class.create();
2795 Flog.RayTracer.Scene.prototype = {
2801 initialize : function() {
2802 this.camera = new Flog.RayTracer.Camera(
2803 new Flog.RayTracer.Vector(0,0,-5),
2804 new Flog.RayTracer.Vector(0,0,1),
2805 new Flog.RayTracer.Vector(0,1,0)
2807 this.shapes = new Array();
2808 this.lights = new Array();
2809 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
2812 /* Fake a Flog.* namespace */
2813 if(typeof(Flog) == 'undefined') var Flog = {};
2814 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2815 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
2817 Flog.RayTracer.Material.BaseMaterial = Class.create();
2819 Flog.RayTracer.Material.BaseMaterial.prototype = {
2821 gloss: 2.0, // [0...infinity] 0 = matt
2822 transparency: 0.0, // 0=opaque
2823 reflection: 0.0, // [0...infinity] 0 = no reflection
2827 initialize : function() {
2831 getColor: function(u, v){
2835 wrapUp: function(t){
2837 if(t < -1) t += 2.0;
2838 if(t >= 1) t -= 2.0;
2842 toString : function () {
2843 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2846 /* Fake a Flog.* namespace */
2847 if(typeof(Flog) == 'undefined') var Flog = {};
2848 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2850 Flog.RayTracer.Material.Solid = Class.create();
2852 Flog.RayTracer.Material.Solid.prototype = Object.extend(
2853 new Flog.RayTracer.Material.BaseMaterial(), {
2854 initialize : function(color, reflection, refraction, transparency, gloss) {
2856 this.reflection = reflection;
2857 this.transparency = transparency;
2859 this.hasTexture = false;
2862 getColor: function(u, v){
2866 toString : function () {
2867 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2871 /* Fake a Flog.* namespace */
2872 if(typeof(Flog) == 'undefined') var Flog = {};
2873 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2875 Flog.RayTracer.Material.Chessboard = Class.create();
2877 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
2878 new Flog.RayTracer.Material.BaseMaterial(), {
2883 initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
2884 this.colorEven = colorEven;
2885 this.colorOdd = colorOdd;
2886 this.reflection = reflection;
2887 this.transparency = transparency;
2889 this.density = density;
2890 this.hasTexture = true;
2893 getColor: function(u, v){
2894 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
2897 return this.colorEven;
2899 return this.colorOdd;
2902 toString : function () {
2903 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2907 /* Fake a Flog.* namespace */
2908 if(typeof(Flog) == 'undefined') var Flog = {};
2909 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2910 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2912 Flog.RayTracer.Shape.BaseShape = Class.create();
2914 Flog.RayTracer.Shape.BaseShape.prototype = {
2918 initialize : function() {
2919 this.position = new Vector(0,0,0);
2920 this.material = new Flog.RayTracer.Material.SolidMaterial(
2921 new Flog.RayTracer.Color(1,0,1),
2928 toString : function () {
2929 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
2932 /* Fake a Flog.* namespace */
2933 if(typeof(Flog) == 'undefined') var Flog = {};
2934 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2935 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2937 Flog.RayTracer.Shape.Sphere = Class.create();
2939 Flog.RayTracer.Shape.Sphere.prototype = {
2940 initialize : function(pos, radius, material) {
2941 this.radius = radius;
2942 this.position = pos;
2943 this.material = material;
2946 intersect: function(ray){
2947 var info = new Flog.RayTracer.IntersectionInfo();
2950 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
2952 var B = dst.dot(ray.direction);
2953 var C = dst.dot(dst) - (this.radius * this.radius);
2954 var D = (B * B) - C;
2956 if(D > 0){ // intersection!
2958 info.distance = (-B) - Math.sqrt(D);
2959 info.position = Flog.RayTracer.Vector.prototype.add(
2961 Flog.RayTracer.Vector.prototype.multiplyScalar(
2966 info.normal = Flog.RayTracer.Vector.prototype.subtract(
2971 info.color = this.material.getColor(0,0);
2978 toString : function () {
2979 return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
2982 /* Fake a Flog.* namespace */
2983 if(typeof(Flog) == 'undefined') var Flog = {};
2984 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
2985 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
2987 Flog.RayTracer.Shape.Plane = Class.create();
2989 Flog.RayTracer.Shape.Plane.prototype = {
2992 initialize : function(pos, d, material) {
2993 this.position = pos;
2995 this.material = material;
2998 intersect: function(ray){
2999 var info = new Flog.RayTracer.IntersectionInfo();
3001 var Vd = this.position.dot(ray.direction);
3002 if(Vd == 0) return info; // no intersection
3004 var t = -(this.position.dot(ray.position) + this.d) / Vd;
3005 if(t <= 0) return info;
3009 info.position = Flog.RayTracer.Vector.prototype.add(
3011 Flog.RayTracer.Vector.prototype.multiplyScalar(
3016 info.normal = this.position;
3019 if(this.material.hasTexture){
3020 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
3021 var vV = vU.cross(this.position);
3022 var u = info.position.dot(vU);
3023 var v = info.position.dot(vV);
3024 info.color = this.material.getColor(u,v);
3026 info.color = this.material.getColor(0,0);
3032 toString : function () {
3033 return 'Plane [' + this.position + ', d=' + this.d + ']';
3036 /* Fake a Flog.* namespace */
3037 if(typeof(Flog) == 'undefined') var Flog = {};
3038 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3040 Flog.RayTracer.IntersectionInfo = Class.create();
3042 Flog.RayTracer.IntersectionInfo.prototype = {
3051 initialize : function() {
3052 this.color = new Flog.RayTracer.Color(0,0,0);
3055 toString : function () {
3056 return 'Intersection [' + this.position + ']';
3059 /* Fake a Flog.* namespace */
3060 if(typeof(Flog) == 'undefined') var Flog = {};
3061 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3063 Flog.RayTracer.Camera = Class.create();
3065 Flog.RayTracer.Camera.prototype = {
3072 initialize : function(pos, lookAt, up) {
3073 this.position = pos;
3074 this.lookAt = lookAt;
3076 this.equator = lookAt.normalize().cross(this.up);
3077 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
3080 getRay: function(vx, vy){
3081 var pos = Flog.RayTracer.Vector.prototype.subtract(
3083 Flog.RayTracer.Vector.prototype.subtract(
3084 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
3085 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
3089 var dir = Flog.RayTracer.Vector.prototype.subtract(
3094 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
3099 toString : function () {
3103 /* Fake a Flog.* namespace */
3104 if(typeof(Flog) == 'undefined') var Flog = {};
3105 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3107 Flog.RayTracer.Background = Class.create();
3109 Flog.RayTracer.Background.prototype = {
3113 initialize : function(color, ambience) {
3115 this.ambience = ambience;
3118 /* Fake a Flog.* namespace */
3119 if(typeof(Flog) == 'undefined') var Flog = {};
3120 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
3122 Flog.RayTracer.Engine = Class.create();
3124 Flog.RayTracer.Engine.prototype = {
3125 canvas: null, /* 2d context we can render to */
3127 initialize: function(options){
3128 this.options = Object.extend({
3133 renderDiffuse: false,
3134 renderShadows: false,
3135 renderHighlights: false,
3136 renderReflections: false,
3140 this.options.canvasHeight /= this.options.pixelHeight;
3141 this.options.canvasWidth /= this.options.pixelWidth;
3143 /* TODO: dynamically include other scripts */
3146 setPixel: function(x, y, color){
3148 pxW = this.options.pixelWidth;
3149 pxH = this.options.pixelHeight;
3152 this.canvas.fillStyle = color.toString();
3153 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
3155 // print(x * pxW, y * pxH, pxW, pxH);
3159 renderScene: function(scene, canvas){
3162 this.canvas = canvas.getContext("2d");
3167 var canvasHeight = this.options.canvasHeight;
3168 var canvasWidth = this.options.canvasWidth;
3170 for(var y=0; y < canvasHeight; y++){
3171 for(var x=0; x < canvasWidth; x++){
3172 var yp = y * 1.0 / canvasHeight * 2 - 1;
3173 var xp = x * 1.0 / canvasWidth * 2 - 1;
3175 var ray = scene.camera.getRay(xp, yp);
3177 var color = this.getPixelColor(ray, scene);
3179 this.setPixel(x, y, color);
3184 getPixelColor: function(ray, scene){
3185 var info = this.testIntersection(ray, scene, null);
3187 var color = this.rayTrace(info, ray, scene, 0);
3190 return scene.background.color;
3193 testIntersection: function(ray, scene, exclude){
3195 var best = new Flog.RayTracer.IntersectionInfo();
3196 best.distance = 2000;
3198 for(var i=0; i<scene.shapes.length; i++){
3199 var shape = scene.shapes[i];
3201 if(shape != exclude){
3202 var info = shape.intersect(ray);
3203 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
3209 best.hitCount = hits;
3213 getReflectionRay: function(P,N,V){
3215 var R1 = Flog.RayTracer.Vector.prototype.add(
3216 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
3219 return new Flog.RayTracer.Ray(P, R1);
3222 rayTrace: function(info, ray, scene, depth){
3224 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
3225 var oldColor = color;
3226 var shininess = Math.pow(10, info.shape.material.gloss + 1);
3228 for(var i=0; i<scene.lights.length; i++){
3229 var light = scene.lights[i];
3231 // Calc diffuse lighting
3232 var v = Flog.RayTracer.Vector.prototype.subtract(
3237 if(this.options.renderDiffuse){
3238 var L = v.dot(info.normal);
3240 color = Flog.RayTracer.Color.prototype.add(
3242 Flog.RayTracer.Color.prototype.multiply(
3244 Flog.RayTracer.Color.prototype.multiplyScalar(
3253 // The greater the depth the more accurate the colours, but
3254 // this is exponentially (!) expensive
3255 if(depth <= this.options.rayDepth){
3256 // calculate reflection ray
3257 if(this.options.renderReflections && info.shape.material.reflection > 0)
3259 var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
3260 var refl = this.testIntersection(reflectionRay, scene, info.shape);
3262 if (refl.isHit && refl.distance > 0){
3263 refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
3265 refl.color = scene.background.color;
3268 color = Flog.RayTracer.Color.prototype.blend(
3271 info.shape.material.reflection
3279 /* Render shadows and highlights */
3281 var shadowInfo = new Flog.RayTracer.IntersectionInfo();
3283 if(this.options.renderShadows){
3284 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
3286 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
3287 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
3288 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
3289 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
3290 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
3294 // Phong specular highlights
3295 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
3296 var Lv = Flog.RayTracer.Vector.prototype.subtract(
3297 info.shape.position,
3301 var E = Flog.RayTracer.Vector.prototype.subtract(
3302 scene.camera.position,
3306 var H = Flog.RayTracer.Vector.prototype.subtract(
3311 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
3312 color = Flog.RayTracer.Color.prototype.add(
3313 Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
3324 function renderScene(){
3325 var scene = new Flog.RayTracer.Scene();
3327 scene.camera = new Flog.RayTracer.Camera(
3328 new Flog.RayTracer.Vector(0, 0, -15),
3329 new Flog.RayTracer.Vector(-0.2, 0, 5),
3330 new Flog.RayTracer.Vector(0, 1, 0)
3333 scene.background = new Flog.RayTracer.Background(
3334 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
3338 var sphere = new Flog.RayTracer.Shape.Sphere(
3339 new Flog.RayTracer.Vector(-1.5, 1.5, 2),
3341 new Flog.RayTracer.Material.Solid(
3342 new Flog.RayTracer.Color(0,0.5,0.5),
3350 var sphere1 = new Flog.RayTracer.Shape.Sphere(
3351 new Flog.RayTracer.Vector(1, 0.25, 1),
3353 new Flog.RayTracer.Material.Solid(
3354 new Flog.RayTracer.Color(0.9,0.9,0.9),
3362 var plane = new Flog.RayTracer.Shape.Plane(
3363 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
3365 new Flog.RayTracer.Material.Chessboard(
3366 new Flog.RayTracer.Color(1,1,1),
3367 new Flog.RayTracer.Color(0,0,0),
3375 scene.shapes.push(plane);
3376 scene.shapes.push(sphere);
3377 scene.shapes.push(sphere1);
3379 var light = new Flog.RayTracer.Light(
3380 new Flog.RayTracer.Vector(5, 10, -1),
3381 new Flog.RayTracer.Color(0.8, 0.8, 0.8)
3384 var light1 = new Flog.RayTracer.Light(
3385 new Flog.RayTracer.Vector(-3, 5, -15),
3386 new Flog.RayTracer.Color(0.8, 0.8, 0.8),
3390 scene.lights.push(light);
3391 scene.lights.push(light1);
3393 var imageWidth = 100; // $F('imageWidth');
3394 var imageHeight = 100; // $F('imageHeight');
3395 var pixelSize = "5,5".split(','); // $F('pixelSize').split(',');
3396 var renderDiffuse = true; // $F('renderDiffuse');
3397 var renderShadows = true; // $F('renderShadows');
3398 var renderHighlights = true; // $F('renderHighlights');
3399 var renderReflections = true; // $F('renderReflections');
3400 var rayDepth = 2;//$F('rayDepth');
3402 var raytracer = new Flog.RayTracer.Engine(
3404 canvasWidth: imageWidth,
3405 canvasHeight: imageHeight,
3406 pixelWidth: pixelSize[0],
3407 pixelHeight: pixelSize[1],
3408 "renderDiffuse": renderDiffuse,
3409 "renderHighlights": renderHighlights,
3410 "renderShadows": renderShadows,
3411 "renderReflections": renderReflections,
3412 "rayDepth": rayDepth
3416 raytracer.renderScene(scene, null, 0);