1 // The ray tracer code in this file is written by Adam Burmister. It
2 // is available in its original form from:
4 // http://labs.flog.nz.co/raytracer/
6 // It has been modified slightly by Google to work as a standalone
7 // benchmark, but the all the computational code remains
8 // untouched. This file also contains a copy of parts of the Prototype
9 // JavaScript framework which is used by the ray tracer.
11 // Variable used to hold a number that can be used to verify that
12 // the scene was ray traced correctly.
16 // ------------------------------------------------------------------------
17 // ------------------------------------------------------------------------
19 // The following is a copy of parts of the Prototype JavaScript library:
21 // Prototype JavaScript framework, version 1.5.0
22 // (c) 2005-2007 Sam Stephenson
24 // Prototype is freely distributable under the terms of an MIT-style license.
25 // For details, see the Prototype web site: http://prototype.conio.net/
31 this.initialize.apply(this, arguments);
37 Object.extend = function(destination, source) {
38 for (var property in source) {
39 destination[property] = source[property];
45 // ------------------------------------------------------------------------
46 // ------------------------------------------------------------------------
48 // The rest of this file is the actual ray tracer written by Adam
49 // Burmister. It's a concatenation of the following files:
56 // flog/material/basematerial.js
57 // flog/material/solid.js
58 // flog/material/chessboard.js
59 // flog/shape/baseshape.js
60 // flog/shape/sphere.js
61 // flog/shape/plane.js
62 // flog/intersectioninfo.js
68 /* Fake a Flog.* namespace */
69 if(typeof(Flog) == 'undefined') var Flog = {};
70 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
72 Flog.RayTracer.Color = Class.create();
74 Flog.RayTracer.Color.prototype = {
79 initialize : function(r, g, b) {
89 add : function(c1, c2){
90 var result = new Flog.RayTracer.Color(0,0,0);
92 result.red = c1.red + c2.red;
93 result.green = c1.green + c2.green;
94 result.blue = c1.blue + c2.blue;
99 addScalar: function(c1, s){
100 var result = new Flog.RayTracer.Color(0,0,0);
102 result.red = c1.red + s;
103 result.green = c1.green + s;
104 result.blue = c1.blue + s;
111 subtract: function(c1, c2){
112 var result = new Flog.RayTracer.Color(0,0,0);
114 result.red = c1.red - c2.red;
115 result.green = c1.green - c2.green;
116 result.blue = c1.blue - c2.blue;
121 multiply : function(c1, c2) {
122 var result = new Flog.RayTracer.Color(0,0,0);
124 result.red = c1.red * c2.red;
125 result.green = c1.green * c2.green;
126 result.blue = c1.blue * c2.blue;
131 multiplyScalar : function(c1, f) {
132 var result = new Flog.RayTracer.Color(0,0,0);
134 result.red = c1.red * f;
135 result.green = c1.green * f;
136 result.blue = c1.blue * f;
141 divideFactor : function(c1, f) {
142 var result = new Flog.RayTracer.Color(0,0,0);
144 result.red = c1.red / f;
145 result.green = c1.green / f;
146 result.blue = c1.blue / f;
152 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0;
153 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0;
154 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0;
157 distance : function(color) {
158 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue);
162 blend: function(c1, c2, w){
163 var result = new Flog.RayTracer.Color(0,0,0);
164 result = Flog.RayTracer.Color.prototype.add(
165 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w),
166 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w)
171 brightness : function() {
172 var r = Math.floor(this.red*255);
173 var g = Math.floor(this.green*255);
174 var b = Math.floor(this.blue*255);
175 return (r * 77 + g * 150 + b * 29) >> 8;
178 toString : function () {
179 var r = Math.floor(this.red*255);
180 var g = Math.floor(this.green*255);
181 var b = Math.floor(this.blue*255);
183 return "rgb("+ r +","+ g +","+ b +")";
186 /* Fake a Flog.* namespace */
187 if(typeof(Flog) == 'undefined') var Flog = {};
188 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
190 Flog.RayTracer.Light = Class.create();
192 Flog.RayTracer.Light.prototype = {
197 initialize : function(pos, color, intensity) {
200 this.intensity = (intensity ? intensity : 10.0);
203 getIntensity: function(distance){
204 if(distance >= intensity) return 0;
206 return Math.pow((intensity - distance) / strength, 0.2);
209 toString : function () {
210 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
213 /* Fake a Flog.* namespace */
214 if(typeof(Flog) == 'undefined') var Flog = {};
215 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
217 Flog.RayTracer.Vector = Class.create();
219 Flog.RayTracer.Vector.prototype = {
224 initialize : function(x, y, z) {
225 this.x = (x ? x : 0);
226 this.y = (y ? y : 0);
227 this.z = (z ? z : 0);
230 copy: function(vector){
236 normalize : function() {
237 var m = this.magnitude();
238 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
241 magnitude : function() {
242 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
245 cross : function(w) {
246 return new Flog.RayTracer.Vector(
247 -this.z * w.y + this.y * w.z,
248 this.z * w.x - this.x * w.z,
249 -this.y * w.x + this.x * w.y);
253 return this.x * w.x + this.y * w.y + this.z * w.z;
256 add : function(v, w) {
257 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
260 subtract : function(v, w) {
261 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
262 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
265 multiplyVector : function(v, w) {
266 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
269 multiplyScalar : function(v, w) {
270 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
273 toString : function () {
274 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
277 /* Fake a Flog.* namespace */
278 if(typeof(Flog) == 'undefined') var Flog = {};
279 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
281 Flog.RayTracer.Ray = Class.create();
283 Flog.RayTracer.Ray.prototype = {
286 initialize : function(pos, dir) {
288 this.direction = dir;
291 toString : function () {
292 return 'Ray [' + this.position + ',' + this.direction + ']';
295 /* Fake a Flog.* namespace */
296 if(typeof(Flog) == 'undefined') var Flog = {};
297 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
299 Flog.RayTracer.Scene = Class.create();
301 Flog.RayTracer.Scene.prototype = {
307 initialize : function() {
308 this.camera = new Flog.RayTracer.Camera(
309 new Flog.RayTracer.Vector(0,0,-5),
310 new Flog.RayTracer.Vector(0,0,1),
311 new Flog.RayTracer.Vector(0,1,0)
313 this.shapes = new Array();
314 this.lights = new Array();
315 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
318 /* Fake a Flog.* namespace */
319 if(typeof(Flog) == 'undefined') var Flog = {};
320 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
321 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
323 Flog.RayTracer.Material.BaseMaterial = Class.create();
325 Flog.RayTracer.Material.BaseMaterial.prototype = {
327 gloss: 2.0, // [0...infinity] 0 = matt
328 transparency: 0.0, // 0=opaque
329 reflection: 0.0, // [0...infinity] 0 = no reflection
333 initialize : function() {
337 getColor: function(u, v){
348 toString : function () {
349 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
352 /* Fake a Flog.* namespace */
353 if(typeof(Flog) == 'undefined') var Flog = {};
354 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
356 Flog.RayTracer.Material.Solid = Class.create();
358 Flog.RayTracer.Material.Solid.prototype = Object.extend(
359 new Flog.RayTracer.Material.BaseMaterial(), {
360 initialize : function(color, reflection, refraction, transparency, gloss) {
362 this.reflection = reflection;
363 this.transparency = transparency;
365 this.hasTexture = false;
368 getColor: function(u, v){
372 toString : function () {
373 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
377 /* Fake a Flog.* namespace */
378 if(typeof(Flog) == 'undefined') var Flog = {};
379 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
381 Flog.RayTracer.Material.Chessboard = Class.create();
383 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
384 new Flog.RayTracer.Material.BaseMaterial(), {
389 initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
390 this.colorEven = colorEven;
391 this.colorOdd = colorOdd;
392 this.reflection = reflection;
393 this.transparency = transparency;
395 this.density = density;
396 this.hasTexture = true;
399 getColor: function(u, v){
400 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
403 return this.colorEven;
405 return this.colorOdd;
408 toString : function () {
409 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
413 /* Fake a Flog.* namespace */
414 if(typeof(Flog) == 'undefined') var Flog = {};
415 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
416 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
418 Flog.RayTracer.Shape.BaseShape = Class.create();
420 Flog.RayTracer.Shape.BaseShape.prototype = {
424 initialize : function() {
425 this.position = new Vector(0,0,0);
426 this.material = new Flog.RayTracer.Material.SolidMaterial(
427 new Flog.RayTracer.Color(1,0,1),
434 toString : function () {
435 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
438 /* Fake a Flog.* namespace */
439 if(typeof(Flog) == 'undefined') var Flog = {};
440 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
441 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
443 Flog.RayTracer.Shape.Sphere = Class.create();
445 Flog.RayTracer.Shape.Sphere.prototype = {
446 initialize : function(pos, radius, material) {
447 this.radius = radius;
449 this.material = material;
452 intersect: function(ray){
453 var info = new Flog.RayTracer.IntersectionInfo();
456 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
458 var B = dst.dot(ray.direction);
459 var C = dst.dot(dst) - (this.radius * this.radius);
462 if(D > 0){ // intersection!
464 info.distance = (-B) - Math.sqrt(D);
465 info.position = Flog.RayTracer.Vector.prototype.add(
467 Flog.RayTracer.Vector.prototype.multiplyScalar(
472 info.normal = Flog.RayTracer.Vector.prototype.subtract(
477 info.color = this.material.getColor(0,0);
484 toString : function () {
485 return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
488 /* Fake a Flog.* namespace */
489 if(typeof(Flog) == 'undefined') var Flog = {};
490 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
491 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
493 Flog.RayTracer.Shape.Plane = Class.create();
495 Flog.RayTracer.Shape.Plane.prototype = {
498 initialize : function(pos, d, material) {
501 this.material = material;
504 intersect: function(ray){
505 var info = new Flog.RayTracer.IntersectionInfo();
507 var Vd = this.position.dot(ray.direction);
508 if(Vd == 0) return info; // no intersection
510 var t = -(this.position.dot(ray.position) + this.d) / Vd;
511 if(t <= 0) return info;
515 info.position = Flog.RayTracer.Vector.prototype.add(
517 Flog.RayTracer.Vector.prototype.multiplyScalar(
522 info.normal = this.position;
525 if(this.material.hasTexture){
526 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
527 var vV = vU.cross(this.position);
528 var u = info.position.dot(vU);
529 var v = info.position.dot(vV);
530 info.color = this.material.getColor(u,v);
532 info.color = this.material.getColor(0,0);
538 toString : function () {
539 return 'Plane [' + this.position + ', d=' + this.d + ']';
542 /* Fake a Flog.* namespace */
543 if(typeof(Flog) == 'undefined') var Flog = {};
544 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
546 Flog.RayTracer.IntersectionInfo = Class.create();
548 Flog.RayTracer.IntersectionInfo.prototype = {
557 initialize : function() {
558 this.color = new Flog.RayTracer.Color(0,0,0);
561 toString : function () {
562 return 'Intersection [' + this.position + ']';
565 /* Fake a Flog.* namespace */
566 if(typeof(Flog) == 'undefined') var Flog = {};
567 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
569 Flog.RayTracer.Camera = Class.create();
571 Flog.RayTracer.Camera.prototype = {
578 initialize : function(pos, lookAt, up) {
580 this.lookAt = lookAt;
582 this.equator = lookAt.normalize().cross(this.up);
583 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
586 getRay: function(vx, vy){
587 var pos = Flog.RayTracer.Vector.prototype.subtract(
589 Flog.RayTracer.Vector.prototype.subtract(
590 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
591 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
595 var dir = Flog.RayTracer.Vector.prototype.subtract(
600 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
605 toString : function () {
609 /* Fake a Flog.* namespace */
610 if(typeof(Flog) == 'undefined') var Flog = {};
611 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
613 Flog.RayTracer.Background = Class.create();
615 Flog.RayTracer.Background.prototype = {
619 initialize : function(color, ambience) {
621 this.ambience = ambience;
624 /* Fake a Flog.* namespace */
625 if(typeof(Flog) == 'undefined') var Flog = {};
626 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
628 Flog.RayTracer.Engine = Class.create();
630 Flog.RayTracer.Engine.prototype = {
631 canvas: null, /* 2d context we can render to */
633 initialize: function(options){
634 this.options = Object.extend({
639 renderDiffuse: false,
640 renderShadows: false,
641 renderHighlights: false,
642 renderReflections: false,
646 this.options.canvasHeight /= this.options.pixelHeight;
647 this.options.canvasWidth /= this.options.pixelWidth;
649 /* TODO: dynamically include other scripts */
652 setPixel: function(x, y, color){
654 pxW = this.options.pixelWidth;
655 pxH = this.options.pixelHeight;
658 this.canvas.fillStyle = color.toString();
659 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
662 checkNumber += color.brightness();
664 // print(x * pxW, y * pxH, pxW, pxH);
668 renderScene: function(scene, canvas){
672 this.canvas = canvas.getContext("2d");
677 var canvasHeight = this.options.canvasHeight;
678 var canvasWidth = this.options.canvasWidth;
680 for(var y=0; y < canvasHeight; y++){
681 for(var x=0; x < canvasWidth; x++){
682 var yp = y * 1.0 / canvasHeight * 2 - 1;
683 var xp = x * 1.0 / canvasWidth * 2 - 1;
685 var ray = scene.camera.getRay(xp, yp);
687 var color = this.getPixelColor(ray, scene);
689 this.setPixel(x, y, color);
692 if (checkNumber !== 2321) {
693 throw new Error("Scene rendered incorrectly");
697 getPixelColor: function(ray, scene){
698 var info = this.testIntersection(ray, scene, null);
700 var color = this.rayTrace(info, ray, scene, 0);
703 return scene.background.color;
706 testIntersection: function(ray, scene, exclude){
708 var best = new Flog.RayTracer.IntersectionInfo();
709 best.distance = 2000;
711 for(var i=0; i<scene.shapes.length; i++){
712 var shape = scene.shapes[i];
714 if(shape != exclude){
715 var info = shape.intersect(ray);
716 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
722 best.hitCount = hits;
726 getReflectionRay: function(P,N,V){
728 var R1 = Flog.RayTracer.Vector.prototype.add(
729 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
732 return new Flog.RayTracer.Ray(P, R1);
735 rayTrace: function(info, ray, scene, depth){
737 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
738 var oldColor = color;
739 var shininess = Math.pow(10, info.shape.material.gloss + 1);
741 for(var i=0; i<scene.lights.length; i++){
742 var light = scene.lights[i];
744 // Calc diffuse lighting
745 var v = Flog.RayTracer.Vector.prototype.subtract(
750 if(this.options.renderDiffuse){
751 var L = v.dot(info.normal);
753 color = Flog.RayTracer.Color.prototype.add(
755 Flog.RayTracer.Color.prototype.multiply(
757 Flog.RayTracer.Color.prototype.multiplyScalar(
766 // The greater the depth the more accurate the colours, but
767 // this is exponentially (!) expensive
768 if(depth <= this.options.rayDepth){
769 // calculate reflection ray
770 if(this.options.renderReflections && info.shape.material.reflection > 0)
772 var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
773 var refl = this.testIntersection(reflectionRay, scene, info.shape);
775 if (refl.isHit && refl.distance > 0){
776 refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
778 refl.color = scene.background.color;
781 color = Flog.RayTracer.Color.prototype.blend(
784 info.shape.material.reflection
792 /* Render shadows and highlights */
794 var shadowInfo = new Flog.RayTracer.IntersectionInfo();
796 if(this.options.renderShadows){
797 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
799 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
800 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
801 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
802 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
803 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
807 // Phong specular highlights
808 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
809 var Lv = Flog.RayTracer.Vector.prototype.subtract(
814 var E = Flog.RayTracer.Vector.prototype.subtract(
815 scene.camera.position,
819 var H = Flog.RayTracer.Vector.prototype.subtract(
824 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
825 color = Flog.RayTracer.Color.prototype.add(
826 Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
837 function renderScene(){
838 var scene = new Flog.RayTracer.Scene();
840 scene.camera = new Flog.RayTracer.Camera(
841 new Flog.RayTracer.Vector(0, 0, -15),
842 new Flog.RayTracer.Vector(-0.2, 0, 5),
843 new Flog.RayTracer.Vector(0, 1, 0)
846 scene.background = new Flog.RayTracer.Background(
847 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
851 var sphere = new Flog.RayTracer.Shape.Sphere(
852 new Flog.RayTracer.Vector(-1.5, 1.5, 2),
854 new Flog.RayTracer.Material.Solid(
855 new Flog.RayTracer.Color(0,0.5,0.5),
863 var sphere1 = new Flog.RayTracer.Shape.Sphere(
864 new Flog.RayTracer.Vector(1, 0.25, 1),
866 new Flog.RayTracer.Material.Solid(
867 new Flog.RayTracer.Color(0.9,0.9,0.9),
875 var plane = new Flog.RayTracer.Shape.Plane(
876 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
878 new Flog.RayTracer.Material.Chessboard(
879 new Flog.RayTracer.Color(1,1,1),
880 new Flog.RayTracer.Color(0,0,0),
888 scene.shapes.push(plane);
889 scene.shapes.push(sphere);
890 scene.shapes.push(sphere1);
892 var light = new Flog.RayTracer.Light(
893 new Flog.RayTracer.Vector(5, 10, -1),
894 new Flog.RayTracer.Color(0.8, 0.8, 0.8)
897 var light1 = new Flog.RayTracer.Light(
898 new Flog.RayTracer.Vector(-3, 5, -15),
899 new Flog.RayTracer.Color(0.8, 0.8, 0.8),
903 scene.lights.push(light);
904 scene.lights.push(light1);
906 var imageWidth = 100; // $F('imageWidth');
907 var imageHeight = 100; // $F('imageHeight');
908 var pixelSize = "5,5".split(','); // $F('pixelSize').split(',');
909 var renderDiffuse = true; // $F('renderDiffuse');
910 var renderShadows = true; // $F('renderShadows');
911 var renderHighlights = true; // $F('renderHighlights');
912 var renderReflections = true; // $F('renderReflections');
913 var rayDepth = 2;//$F('rayDepth');
915 var raytracer = new Flog.RayTracer.Engine(
917 canvasWidth: imageWidth,
918 canvasHeight: imageHeight,
919 pixelWidth: pixelSize[0],
920 pixelHeight: pixelSize[1],
921 "renderDiffuse": renderDiffuse,
922 "renderHighlights": renderHighlights,
923 "renderShadows": renderShadows,
924 "renderReflections": renderReflections,
929 raytracer.renderScene(scene, null, 0);
932 for (var i = 0; i < 6; ++i)