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