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 toString : function () {
204 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
207 /* Fake a Flog.* namespace */
208 if(typeof(Flog) == 'undefined') var Flog = {};
209 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
211 Flog.RayTracer.Vector = Class.create();
213 Flog.RayTracer.Vector.prototype = {
218 initialize : function(x, y, z) {
219 this.x = (x ? x : 0);
220 this.y = (y ? y : 0);
221 this.z = (z ? z : 0);
224 copy: function(vector){
230 normalize : function() {
231 var m = this.magnitude();
232 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
235 magnitude : function() {
236 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
239 cross : function(w) {
240 return new Flog.RayTracer.Vector(
241 -this.z * w.y + this.y * w.z,
242 this.z * w.x - this.x * w.z,
243 -this.y * w.x + this.x * w.y);
247 return this.x * w.x + this.y * w.y + this.z * w.z;
250 add : function(v, w) {
251 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
254 subtract : function(v, w) {
255 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
256 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
259 multiplyVector : function(v, w) {
260 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
263 multiplyScalar : function(v, w) {
264 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
267 toString : function () {
268 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
271 /* Fake a Flog.* namespace */
272 if(typeof(Flog) == 'undefined') var Flog = {};
273 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
275 Flog.RayTracer.Ray = Class.create();
277 Flog.RayTracer.Ray.prototype = {
280 initialize : function(pos, dir) {
282 this.direction = dir;
285 toString : function () {
286 return 'Ray [' + this.position + ',' + this.direction + ']';
289 /* Fake a Flog.* namespace */
290 if(typeof(Flog) == 'undefined') var Flog = {};
291 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
293 Flog.RayTracer.Scene = Class.create();
295 Flog.RayTracer.Scene.prototype = {
301 initialize : function() {
302 this.camera = new Flog.RayTracer.Camera(
303 new Flog.RayTracer.Vector(0,0,-5),
304 new Flog.RayTracer.Vector(0,0,1),
305 new Flog.RayTracer.Vector(0,1,0)
307 this.shapes = new Array();
308 this.lights = new Array();
309 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
312 /* Fake a Flog.* namespace */
313 if(typeof(Flog) == 'undefined') var Flog = {};
314 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
315 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
317 Flog.RayTracer.Material.BaseMaterial = Class.create();
319 Flog.RayTracer.Material.BaseMaterial.prototype = {
321 gloss: 2.0, // [0...infinity] 0 = matt
322 transparency: 0.0, // 0=opaque
323 reflection: 0.0, // [0...infinity] 0 = no reflection
327 initialize : function() {
331 getColor: function(u, v){
342 toString : function () {
343 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
346 /* Fake a Flog.* namespace */
347 if(typeof(Flog) == 'undefined') var Flog = {};
348 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
350 Flog.RayTracer.Material.Solid = Class.create();
352 Flog.RayTracer.Material.Solid.prototype = Object.extend(
353 new Flog.RayTracer.Material.BaseMaterial(), {
354 initialize : function(color, reflection, refraction, transparency, gloss) {
356 this.reflection = reflection;
357 this.transparency = transparency;
359 this.hasTexture = false;
362 getColor: function(u, v){
366 toString : function () {
367 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
371 /* Fake a Flog.* namespace */
372 if(typeof(Flog) == 'undefined') var Flog = {};
373 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
375 Flog.RayTracer.Material.Chessboard = Class.create();
377 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
378 new Flog.RayTracer.Material.BaseMaterial(), {
383 initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
384 this.colorEven = colorEven;
385 this.colorOdd = colorOdd;
386 this.reflection = reflection;
387 this.transparency = transparency;
389 this.density = density;
390 this.hasTexture = true;
393 getColor: function(u, v){
394 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
397 return this.colorEven;
399 return this.colorOdd;
402 toString : function () {
403 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
407 /* Fake a Flog.* namespace */
408 if(typeof(Flog) == 'undefined') var Flog = {};
409 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
410 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
412 Flog.RayTracer.Shape.Sphere = Class.create();
414 Flog.RayTracer.Shape.Sphere.prototype = {
415 initialize : function(pos, radius, material) {
416 this.radius = radius;
418 this.material = material;
421 intersect: function(ray){
422 var info = new Flog.RayTracer.IntersectionInfo();
425 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
427 var B = dst.dot(ray.direction);
428 var C = dst.dot(dst) - (this.radius * this.radius);
431 if(D > 0){ // intersection!
433 info.distance = (-B) - Math.sqrt(D);
434 info.position = Flog.RayTracer.Vector.prototype.add(
436 Flog.RayTracer.Vector.prototype.multiplyScalar(
441 info.normal = Flog.RayTracer.Vector.prototype.subtract(
446 info.color = this.material.getColor(0,0);
453 toString : function () {
454 return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
457 /* Fake a Flog.* namespace */
458 if(typeof(Flog) == 'undefined') var Flog = {};
459 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
460 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
462 Flog.RayTracer.Shape.Plane = Class.create();
464 Flog.RayTracer.Shape.Plane.prototype = {
467 initialize : function(pos, d, material) {
470 this.material = material;
473 intersect: function(ray){
474 var info = new Flog.RayTracer.IntersectionInfo();
476 var Vd = this.position.dot(ray.direction);
477 if(Vd == 0) return info; // no intersection
479 var t = -(this.position.dot(ray.position) + this.d) / Vd;
480 if(t <= 0) return info;
484 info.position = Flog.RayTracer.Vector.prototype.add(
486 Flog.RayTracer.Vector.prototype.multiplyScalar(
491 info.normal = this.position;
494 if(this.material.hasTexture){
495 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
496 var vV = vU.cross(this.position);
497 var u = info.position.dot(vU);
498 var v = info.position.dot(vV);
499 info.color = this.material.getColor(u,v);
501 info.color = this.material.getColor(0,0);
507 toString : function () {
508 return 'Plane [' + this.position + ', d=' + this.d + ']';
511 /* Fake a Flog.* namespace */
512 if(typeof(Flog) == 'undefined') var Flog = {};
513 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
515 Flog.RayTracer.IntersectionInfo = Class.create();
517 Flog.RayTracer.IntersectionInfo.prototype = {
526 initialize : function() {
527 this.color = new Flog.RayTracer.Color(0,0,0);
530 toString : function () {
531 return 'Intersection [' + this.position + ']';
534 /* Fake a Flog.* namespace */
535 if(typeof(Flog) == 'undefined') var Flog = {};
536 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
538 Flog.RayTracer.Camera = Class.create();
540 Flog.RayTracer.Camera.prototype = {
547 initialize : function(pos, lookAt, up) {
549 this.lookAt = lookAt;
551 this.equator = lookAt.normalize().cross(this.up);
552 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
555 getRay: function(vx, vy){
556 var pos = Flog.RayTracer.Vector.prototype.subtract(
558 Flog.RayTracer.Vector.prototype.subtract(
559 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
560 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
564 var dir = Flog.RayTracer.Vector.prototype.subtract(
569 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
574 toString : function () {
578 /* Fake a Flog.* namespace */
579 if(typeof(Flog) == 'undefined') var Flog = {};
580 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
582 Flog.RayTracer.Background = Class.create();
584 Flog.RayTracer.Background.prototype = {
588 initialize : function(color, ambience) {
590 this.ambience = ambience;
593 /* Fake a Flog.* namespace */
594 if(typeof(Flog) == 'undefined') var Flog = {};
595 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
597 Flog.RayTracer.Engine = Class.create();
599 Flog.RayTracer.Engine.prototype = {
600 canvas: null, /* 2d context we can render to */
602 initialize: function(options){
603 this.options = Object.extend({
608 renderDiffuse: false,
609 renderShadows: false,
610 renderHighlights: false,
611 renderReflections: false,
615 this.options.canvasHeight /= this.options.pixelHeight;
616 this.options.canvasWidth /= this.options.pixelWidth;
618 /* TODO: dynamically include other scripts */
621 setPixel: function(x, y, color){
623 pxW = this.options.pixelWidth;
624 pxH = this.options.pixelHeight;
627 this.canvas.fillStyle = color.toString();
628 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
631 checkNumber += color.brightness();
633 // print(x * pxW, y * pxH, pxW, pxH);
637 renderScene: function(scene, canvas){
641 this.canvas = canvas.getContext("2d");
646 var canvasHeight = this.options.canvasHeight;
647 var canvasWidth = this.options.canvasWidth;
649 for(var y=0; y < canvasHeight; y++){
650 for(var x=0; x < canvasWidth; x++){
651 var yp = y * 1.0 / canvasHeight * 2 - 1;
652 var xp = x * 1.0 / canvasWidth * 2 - 1;
654 var ray = scene.camera.getRay(xp, yp);
656 var color = this.getPixelColor(ray, scene);
658 this.setPixel(x, y, color);
661 if (checkNumber !== 2321) {
662 throw new Error("Scene rendered incorrectly");
666 getPixelColor: function(ray, scene){
667 var info = this.testIntersection(ray, scene, null);
669 var color = this.rayTrace(info, ray, scene, 0);
672 return scene.background.color;
675 testIntersection: function(ray, scene, exclude){
677 var best = new Flog.RayTracer.IntersectionInfo();
678 best.distance = 2000;
680 for(var i=0; i<scene.shapes.length; i++){
681 var shape = scene.shapes[i];
683 if(shape != exclude){
684 var info = shape.intersect(ray);
685 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
691 best.hitCount = hits;
695 getReflectionRay: function(P,N,V){
697 var R1 = Flog.RayTracer.Vector.prototype.add(
698 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
701 return new Flog.RayTracer.Ray(P, R1);
704 rayTrace: function(info, ray, scene, depth){
706 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
707 var oldColor = color;
708 var shininess = Math.pow(10, info.shape.material.gloss + 1);
710 for(var i=0; i<scene.lights.length; i++){
711 var light = scene.lights[i];
713 // Calc diffuse lighting
714 var v = Flog.RayTracer.Vector.prototype.subtract(
719 if(this.options.renderDiffuse){
720 var L = v.dot(info.normal);
722 color = Flog.RayTracer.Color.prototype.add(
724 Flog.RayTracer.Color.prototype.multiply(
726 Flog.RayTracer.Color.prototype.multiplyScalar(
735 // The greater the depth the more accurate the colours, but
736 // this is exponentially (!) expensive
737 if(depth <= this.options.rayDepth){
738 // calculate reflection ray
739 if(this.options.renderReflections && info.shape.material.reflection > 0)
741 var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
742 var refl = this.testIntersection(reflectionRay, scene, info.shape);
744 if (refl.isHit && refl.distance > 0){
745 refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
747 refl.color = scene.background.color;
750 color = Flog.RayTracer.Color.prototype.blend(
753 info.shape.material.reflection
761 /* Render shadows and highlights */
763 var shadowInfo = new Flog.RayTracer.IntersectionInfo();
765 if(this.options.renderShadows){
766 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
768 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
769 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
770 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
771 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
772 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
776 // Phong specular highlights
777 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
778 var Lv = Flog.RayTracer.Vector.prototype.subtract(
783 var E = Flog.RayTracer.Vector.prototype.subtract(
784 scene.camera.position,
788 var H = Flog.RayTracer.Vector.prototype.subtract(
793 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
794 color = Flog.RayTracer.Color.prototype.add(
795 Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
806 function renderScene(){
807 var scene = new Flog.RayTracer.Scene();
809 scene.camera = new Flog.RayTracer.Camera(
810 new Flog.RayTracer.Vector(0, 0, -15),
811 new Flog.RayTracer.Vector(-0.2, 0, 5),
812 new Flog.RayTracer.Vector(0, 1, 0)
815 scene.background = new Flog.RayTracer.Background(
816 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
820 var sphere = new Flog.RayTracer.Shape.Sphere(
821 new Flog.RayTracer.Vector(-1.5, 1.5, 2),
823 new Flog.RayTracer.Material.Solid(
824 new Flog.RayTracer.Color(0,0.5,0.5),
832 var sphere1 = new Flog.RayTracer.Shape.Sphere(
833 new Flog.RayTracer.Vector(1, 0.25, 1),
835 new Flog.RayTracer.Material.Solid(
836 new Flog.RayTracer.Color(0.9,0.9,0.9),
844 var plane = new Flog.RayTracer.Shape.Plane(
845 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
847 new Flog.RayTracer.Material.Chessboard(
848 new Flog.RayTracer.Color(1,1,1),
849 new Flog.RayTracer.Color(0,0,0),
857 scene.shapes.push(plane);
858 scene.shapes.push(sphere);
859 scene.shapes.push(sphere1);
861 var light = new Flog.RayTracer.Light(
862 new Flog.RayTracer.Vector(5, 10, -1),
863 new Flog.RayTracer.Color(0.8, 0.8, 0.8)
866 var light1 = new Flog.RayTracer.Light(
867 new Flog.RayTracer.Vector(-3, 5, -15),
868 new Flog.RayTracer.Color(0.8, 0.8, 0.8),
872 scene.lights.push(light);
873 scene.lights.push(light1);
875 var imageWidth = 100; // $F('imageWidth');
876 var imageHeight = 100; // $F('imageHeight');
877 var pixelSize = "5,5".split(','); // $F('pixelSize').split(',');
878 var renderDiffuse = true; // $F('renderDiffuse');
879 var renderShadows = true; // $F('renderShadows');
880 var renderHighlights = true; // $F('renderHighlights');
881 var renderReflections = true; // $F('renderReflections');
882 var rayDepth = 2;//$F('rayDepth');
884 var raytracer = new Flog.RayTracer.Engine(
886 canvasWidth: imageWidth,
887 canvasHeight: imageHeight,
888 pixelWidth: pixelSize[0],
889 pixelHeight: pixelSize[1],
890 "renderDiffuse": renderDiffuse,
891 "renderHighlights": renderHighlights,
892 "renderShadows": renderShadows,
893 "renderReflections": renderReflections,
898 raytracer.renderScene(scene, null, 0);
901 for (var i = 0; i < 6; ++i)