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