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);
38 Object.extend = function(destination, source) {
39 for (var property in source) {
41 destination[property] = source[property];
48 // ------------------------------------------------------------------------
49 // ------------------------------------------------------------------------
51 // The rest of this file is the actual ray tracer written by Adam
52 // Burmister. It's a concatenation of the following files:
59 // flog/material/basematerial.js
60 // flog/material/solid.js
61 // flog/material/chessboard.js
62 // flog/shape/baseshape.js
63 // flog/shape/sphere.js
64 // flog/shape/plane.js
65 // flog/intersectioninfo.js
71 /* Fake a Flog.* namespace */
72 if(typeof(Flog) == 'undefined') var Flog = {};
73 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
75 Flog.RayTracer.Color = Class.create();
77 Flog.RayTracer.Color.prototype = {
82 initialize : function(r, g, b) {
94 add : function(c1, c2){
96 var result = new Flog.RayTracer.Color(0,0,0);
98 result.red = c1.red + c2.red;
99 result.green = c1.green + c2.green;
100 result.blue = c1.blue + c2.blue;
107 addScalar: function(c1, s){
109 var result = new Flog.RayTracer.Color(0,0,0);
111 result.red = c1.red + s;
112 result.green = c1.green + s;
113 result.blue = c1.blue + s;
122 subtract: function(c1, c2){
124 var result = new Flog.RayTracer.Color(0,0,0);
126 result.red = c1.red - c2.red;
127 result.green = c1.green - c2.green;
128 result.blue = c1.blue - c2.blue;
135 multiply : function(c1, c2) {
137 var result = new Flog.RayTracer.Color(0,0,0);
139 result.red = c1.red * c2.red;
140 result.green = c1.green * c2.green;
141 result.blue = c1.blue * c2.blue;
148 multiplyScalar : function(c1, f) {
150 var result = new Flog.RayTracer.Color(0,0,0);
152 result.red = c1.red * f;
153 result.green = c1.green * f;
154 result.blue = c1.blue * f;
161 divideFactor : function(c1, f) {
163 var result = new Flog.RayTracer.Color(0,0,0);
165 result.red = c1.red / f;
166 result.green = c1.green / f;
167 result.blue = c1.blue / f;
176 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0;
177 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0;
178 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0;
183 distance : function(color) {
185 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue);
190 blend: function(c1, c2, w){
192 var result = new Flog.RayTracer.Color(0,0,0);
193 result = Flog.RayTracer.Color.prototype.add(
194 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w),
195 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w)
201 brightness : function() {
203 var r = Math.floor(this.red*255);
204 var g = Math.floor(this.green*255);
205 var b = Math.floor(this.blue*255);
207 return (r * 77 + g * 150 + b * 29) >> 8;
210 toString : function () {
212 var r = Math.floor(this.red*255);
213 var g = Math.floor(this.green*255);
214 var b = Math.floor(this.blue*255);
217 return "rgb("+ r +","+ g +","+ b +")";
220 /* Fake a Flog.* namespace */
221 if(typeof(Flog) == 'undefined') var Flog = {};
222 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
224 Flog.RayTracer.Light = Class.create();
226 Flog.RayTracer.Light.prototype = {
231 initialize : function(pos, color, intensity) {
235 this.intensity = (intensity ? intensity : 10.0);
240 toString : function () {
242 var result = 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']';
247 /* Fake a Flog.* namespace */
248 if(typeof(Flog) == 'undefined') var Flog = {};
249 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
251 Flog.RayTracer.Vector = Class.create();
253 Flog.RayTracer.Vector.prototype = {
258 initialize : function(x, y, z) {
260 this.x = (x ? x : 0);
261 this.y = (y ? y : 0);
262 this.z = (z ? z : 0);
266 copy: function(vector){
274 normalize : function() {
276 var m = this.magnitude();
277 var result = new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m);
282 magnitude : function() {
284 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
288 cross : function(w) {
290 return new Flog.RayTracer.Vector(
291 -this.z * w.y + this.y * w.z,
292 this.z * w.x - this.x * w.z,
293 -this.y * w.x + this.x * w.y);
299 return this.x * w.x + this.y * w.y + this.z * w.z;
303 add : function(v, w) {
305 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z);
309 subtract : function(v, w) {
311 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']';
312 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z);
316 multiplyVector : function(v, w) {
318 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z);
322 multiplyScalar : function(v, w) {
324 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w);
328 toString : function () {
330 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
334 /* Fake a Flog.* namespace */
335 if(typeof(Flog) == 'undefined') var Flog = {};
336 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
338 Flog.RayTracer.Ray = Class.create();
340 Flog.RayTracer.Ray.prototype = {
343 initialize : function(pos, dir) {
346 this.direction = dir;
350 toString : function () {
352 return 'Ray [' + this.position + ',' + this.direction + ']';
356 /* Fake a Flog.* namespace */
357 if(typeof(Flog) == 'undefined') var Flog = {};
358 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
360 Flog.RayTracer.Scene = Class.create();
362 Flog.RayTracer.Scene.prototype = {
368 initialize : function() {
370 this.camera = new Flog.RayTracer.Camera(
371 new Flog.RayTracer.Vector(0,0,-5),
372 new Flog.RayTracer.Vector(0,0,1),
373 new Flog.RayTracer.Vector(0,1,0)
375 this.shapes = new Array();
376 this.lights = new Array();
377 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2);
382 /* Fake a Flog.* namespace */
383 if(typeof(Flog) == 'undefined') var Flog = {};
384 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
385 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {};
387 Flog.RayTracer.Material.BaseMaterial = Class.create();
389 Flog.RayTracer.Material.BaseMaterial.prototype = {
391 gloss: 2.0, // [0...infinity] 0 = matt
392 transparency: 0.0, // 0=opaque
393 reflection: 0.0, // [0...infinity] 0 = no reflection
397 initialize : function() {
401 getColor: function(u, v){
414 toString : function () {
416 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
420 /* Fake a Flog.* namespace */
421 if(typeof(Flog) == 'undefined') var Flog = {};
422 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
424 Flog.RayTracer.Material.Solid = Class.create();
426 Flog.RayTracer.Material.Solid.prototype = Object.extend(
427 new Flog.RayTracer.Material.BaseMaterial(), {
428 initialize : function(color, reflection, refraction, transparency, gloss) {
431 this.reflection = reflection;
432 this.transparency = transparency;
434 this.hasTexture = false;
438 getColor: function(u, v){
444 toString : function () {
446 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
451 /* Fake a Flog.* namespace */
452 if(typeof(Flog) == 'undefined') var Flog = {};
453 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
455 Flog.RayTracer.Material.Chessboard = Class.create();
457 Flog.RayTracer.Material.Chessboard.prototype = Object.extend(
458 new Flog.RayTracer.Material.BaseMaterial(), {
463 initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) {
465 this.colorEven = colorEven;
466 this.colorOdd = colorOdd;
467 this.reflection = reflection;
468 this.transparency = transparency;
470 this.density = density;
471 this.hasTexture = true;
475 getColor: function(u, v){
477 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
481 return this.colorEven;
483 return this.colorOdd;
486 toString : function () {
488 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']';
493 /* Fake a Flog.* namespace */
494 if(typeof(Flog) == 'undefined') var Flog = {};
495 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
496 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
498 Flog.RayTracer.Shape.Sphere = Class.create();
500 Flog.RayTracer.Shape.Sphere.prototype = {
501 initialize : function(pos, radius, material) {
503 this.radius = radius;
505 this.material = material;
510 intersect: function(ray){
512 var info = new Flog.RayTracer.IntersectionInfo();
515 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position);
517 var B = dst.dot(ray.direction);
518 var C = dst.dot(dst) - (this.radius * this.radius);
521 if(D > 0){ // intersection!
523 info.distance = (-B) - Math.sqrt(D);
524 info.position = Flog.RayTracer.Vector.prototype.add(
526 Flog.RayTracer.Vector.prototype.multiplyScalar(
531 info.normal = Flog.RayTracer.Vector.prototype.subtract(
536 info.color = this.material.getColor(0,0);
545 toString : function () {
547 return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']';
551 /* Fake a Flog.* namespace */
552 if(typeof(Flog) == 'undefined') var Flog = {};
553 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
554 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {};
556 Flog.RayTracer.Shape.Plane = Class.create();
558 Flog.RayTracer.Shape.Plane.prototype = {
561 initialize : function(pos, d, material) {
565 this.material = material;
569 intersect: function(ray){
571 var info = new Flog.RayTracer.IntersectionInfo();
573 var Vd = this.position.dot(ray.direction);
574 if(Vd == 0) return info; // no intersection
576 var t = -(this.position.dot(ray.position) + this.d) / Vd;
577 if(t <= 0) return info;
581 info.position = Flog.RayTracer.Vector.prototype.add(
583 Flog.RayTracer.Vector.prototype.multiplyScalar(
588 info.normal = this.position;
591 if(this.material.hasTexture){
592 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x);
593 var vV = vU.cross(this.position);
594 var u = info.position.dot(vU);
595 var v = info.position.dot(vV);
596 info.color = this.material.getColor(u,v);
598 info.color = this.material.getColor(0,0);
605 toString : function () {
607 return 'Plane [' + this.position + ', d=' + this.d + ']';
611 /* Fake a Flog.* namespace */
612 if(typeof(Flog) == 'undefined') var Flog = {};
613 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
615 Flog.RayTracer.IntersectionInfo = Class.create();
617 Flog.RayTracer.IntersectionInfo.prototype = {
626 initialize : function() {
628 this.color = new Flog.RayTracer.Color(0,0,0);
632 toString : function () {
634 return 'Intersection [' + this.position + ']';
638 /* Fake a Flog.* namespace */
639 if(typeof(Flog) == 'undefined') var Flog = {};
640 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
642 Flog.RayTracer.Camera = Class.create();
644 Flog.RayTracer.Camera.prototype = {
651 initialize : function(pos, lookAt, up) {
654 this.lookAt = lookAt;
656 this.equator = lookAt.normalize().cross(this.up);
657 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt);
661 getRay: function(vx, vy){
663 var pos = Flog.RayTracer.Vector.prototype.subtract(
665 Flog.RayTracer.Vector.prototype.subtract(
666 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx),
667 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy)
671 var dir = Flog.RayTracer.Vector.prototype.subtract(
676 var ray = new Flog.RayTracer.Ray(pos, dir.normalize());
682 toString : function () {
688 /* Fake a Flog.* namespace */
689 if(typeof(Flog) == 'undefined') var Flog = {};
690 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
692 Flog.RayTracer.Background = Class.create();
694 Flog.RayTracer.Background.prototype = {
698 initialize : function(color, ambience) {
701 this.ambience = ambience;
705 /* Fake a Flog.* namespace */
706 if(typeof(Flog) == 'undefined') var Flog = {};
707 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {};
709 Flog.RayTracer.Engine = Class.create();
711 Flog.RayTracer.Engine.prototype = {
712 canvas: null, /* 2d context we can render to */
714 initialize: function(options){
716 this.options = Object.extend({
721 renderDiffuse: false,
722 renderShadows: false,
723 renderHighlights: false,
724 renderReflections: false,
728 this.options.canvasHeight /= this.options.pixelHeight;
729 this.options.canvasWidth /= this.options.pixelWidth;
733 /* TODO: dynamically include other scripts */
736 setPixel: function(x, y, color){
739 pxW = this.options.pixelWidth;
740 pxH = this.options.pixelHeight;
743 this.canvas.fillStyle = color.toString();
744 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH);
747 checkNumber += color.brightness();
749 // print(x * pxW, y * pxH, pxW, pxH);
755 renderScene: function(scene, canvas){
760 this.canvas = canvas.getContext("2d");
765 var canvasHeight = this.options.canvasHeight;
766 var canvasWidth = this.options.canvasWidth;
768 for(var y=0; y < canvasHeight; y++){
769 for(var x=0; x < canvasWidth; x++){
771 var yp = y * 1.0 / canvasHeight * 2 - 1;
772 var xp = x * 1.0 / canvasWidth * 2 - 1;
774 var ray = scene.camera.getRay(xp, yp);
776 var color = this.getPixelColor(ray, scene);
778 this.setPixel(x, y, color);
784 if (checkNumber !== 2321) {
785 throw new Error("Scene rendered incorrectly");
789 getPixelColor: function(ray, scene){
791 var info = this.testIntersection(ray, scene, null);
793 var color = this.rayTrace(info, ray, scene, 0);
796 return scene.background.color;
800 testIntersection: function(ray, scene, exclude){
803 var best = new Flog.RayTracer.IntersectionInfo();
804 best.distance = 2000;
806 for(var i=0; i<scene.shapes.length; i++){
808 var shape = scene.shapes[i];
810 if(shape != exclude){
811 var info = shape.intersect(ray);
812 if(info.isHit && info.distance >= 0 && info.distance < best.distance){
820 best.hitCount = hits;
826 getReflectionRay: function(P,N,V){
829 var R1 = Flog.RayTracer.Vector.prototype.add(
830 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1),
835 return new Flog.RayTracer.Ray(P, R1);
838 rayTrace: function(info, ray, scene, depth){
841 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience);
842 var oldColor = color;
843 var shininess = Math.pow(10, info.shape.material.gloss + 1);
845 for(var i=0; i<scene.lights.length; i++){
847 var light = scene.lights[i];
849 // Calc diffuse lighting
850 var v = Flog.RayTracer.Vector.prototype.subtract(
855 if(this.options.renderDiffuse){
856 var L = v.dot(info.normal);
858 color = Flog.RayTracer.Color.prototype.add(
860 Flog.RayTracer.Color.prototype.multiply(
862 Flog.RayTracer.Color.prototype.multiplyScalar(
874 // The greater the depth the more accurate the colours, but
875 // this is exponentially (!) expensive
876 if(depth <= this.options.rayDepth){
877 // calculate reflection ray
878 if(this.options.renderReflections && info.shape.material.reflection > 0)
880 var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
881 var refl = this.testIntersection(reflectionRay, scene, info.shape);
883 if (refl.isHit && refl.distance > 0){
884 refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
886 refl.color = scene.background.color;
889 color = Flog.RayTracer.Color.prototype.blend(
892 info.shape.material.reflection
901 /* Render shadows and highlights */
903 var shadowInfo = new Flog.RayTracer.IntersectionInfo();
905 if(this.options.renderShadows){
906 var shadowRay = new Flog.RayTracer.Ray(info.position, v);
908 shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
909 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){
910 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5);
911 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5));
912 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB);
917 // Phong specular highlights
918 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){
919 var Lv = Flog.RayTracer.Vector.prototype.subtract(
924 var E = Flog.RayTracer.Vector.prototype.subtract(
925 scene.camera.position,
929 var H = Flog.RayTracer.Vector.prototype.subtract(
934 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess);
935 color = Flog.RayTracer.Color.prototype.add(
936 Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight),
950 function renderScene(){
952 var scene = new Flog.RayTracer.Scene();
954 scene.camera = new Flog.RayTracer.Camera(
955 new Flog.RayTracer.Vector(0, 0, -15),
956 new Flog.RayTracer.Vector(-0.2, 0, 5),
957 new Flog.RayTracer.Vector(0, 1, 0)
960 scene.background = new Flog.RayTracer.Background(
961 new Flog.RayTracer.Color(0.5, 0.5, 0.5),
965 var sphere = new Flog.RayTracer.Shape.Sphere(
966 new Flog.RayTracer.Vector(-1.5, 1.5, 2),
968 new Flog.RayTracer.Material.Solid(
969 new Flog.RayTracer.Color(0,0.5,0.5),
977 var sphere1 = new Flog.RayTracer.Shape.Sphere(
978 new Flog.RayTracer.Vector(1, 0.25, 1),
980 new Flog.RayTracer.Material.Solid(
981 new Flog.RayTracer.Color(0.9,0.9,0.9),
989 var plane = new Flog.RayTracer.Shape.Plane(
990 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(),
992 new Flog.RayTracer.Material.Chessboard(
993 new Flog.RayTracer.Color(1,1,1),
994 new Flog.RayTracer.Color(0,0,0),
1002 scene.shapes.push(plane);
1003 scene.shapes.push(sphere);
1004 scene.shapes.push(sphere1);
1006 var light = new Flog.RayTracer.Light(
1007 new Flog.RayTracer.Vector(5, 10, -1),
1008 new Flog.RayTracer.Color(0.8, 0.8, 0.8)
1011 var light1 = new Flog.RayTracer.Light(
1012 new Flog.RayTracer.Vector(-3, 5, -15),
1013 new Flog.RayTracer.Color(0.8, 0.8, 0.8),
1017 scene.lights.push(light);
1018 scene.lights.push(light1);
1020 var imageWidth = 100; // $F('imageWidth');
1021 var imageHeight = 100; // $F('imageHeight');
1022 var pixelSize = "5,5".split(','); // $F('pixelSize').split(',');
1023 var renderDiffuse = true; // $F('renderDiffuse');
1024 var renderShadows = true; // $F('renderShadows');
1025 var renderHighlights = true; // $F('renderHighlights');
1026 var renderReflections = true; // $F('renderReflections');
1027 var rayDepth = 2;//$F('rayDepth');
1029 var raytracer = new Flog.RayTracer.Engine(
1031 canvasWidth: imageWidth,
1032 canvasHeight: imageHeight,
1033 pixelWidth: pixelSize[0],
1034 pixelHeight: pixelSize[1],
1035 "renderDiffuse": renderDiffuse,
1036 "renderHighlights": renderHighlights,
1037 "renderShadows": renderShadows,
1038 "renderReflections": renderReflections,
1039 "rayDepth": rayDepth
1043 raytracer.renderScene(scene, null, 0);
1047 for (var i = 0; i < 6; ++i)