3 <script src=
"../htmlrunner.js"></script>
6 * Copyright (C) 2007 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 function createVector(x
,y
,z
) {
31 return new Array(x
,y
,z
);
34 function sqrLengthVector(self
) {
35 return self
[0] * self
[0] + self
[1] * self
[1] + self
[2] * self
[2];
38 function lengthVector(self
) {
39 return Math
.sqrt(self
[0] * self
[0] + self
[1] * self
[1] + self
[2] * self
[2]);
42 function addVector(self
, v
) {
49 function subVector(self
, v
) {
56 function scaleVector(self
, scale
) {
63 function normaliseVector(self
) {
64 var len
= Math
.sqrt(self
[0] * self
[0] + self
[1] * self
[1] + self
[2] * self
[2]);
71 function add(v1
, v2
) {
72 return new Array(v1
[0] + v2
[0], v1
[1] + v2
[1], v1
[2] + v2
[2]);
75 function sub(v1
, v2
) {
76 return new Array(v1
[0] - v2
[0], v1
[1] - v2
[1], v1
[2] - v2
[2]);
79 function scalev(v1
, v2
) {
80 return new Array(v1
[0] * v2
[0], v1
[1] * v2
[1], v1
[2] * v2
[2]);
83 function dot(v1
, v2
) {
84 return v1
[0] * v2
[0] + v1
[1] * v2
[1] + v1
[2] * v2
[2];
87 function scale(v
, scale
) {
88 return [v
[0] * scale
, v
[1] * scale
, v
[2] * scale
];
91 function cross(v1
, v2
) {
92 return [v1
[1] * v2
[2] - v1
[2] * v2
[1],
93 v1
[2] * v2
[0] - v1
[0] * v2
[2],
94 v1
[0] * v2
[1] - v1
[1] * v2
[0]];
98 function normalise(v
) {
99 var len
= lengthVector(v
);
100 return [v
[0] / len
, v
[1] / len
, v
[2] / len
];
103 function transformMatrix(self
, v
) {
105 var x
= vals
[0] * v
[0] + vals
[1] * v
[1] + vals
[2] * v
[2] + vals
[3];
106 var y
= vals
[4] * v
[0] + vals
[5] * v
[1] + vals
[6] * v
[2] + vals
[7];
107 var z
= vals
[8] * v
[0] + vals
[9] * v
[1] + vals
[10] * v
[2] + vals
[11];
111 function invertMatrix(self
) {
112 var temp
= new Array(16);
116 for (h
= 0; h
< 3; h
++)
117 for (v
= 0; v
< 3; v
++)
118 temp
[h
+ v
* 4] = self
[v
+ h
* 4];
119 for (i
= 0; i
< 11; i
++)
121 self
[3] = tx
* self
[0] + ty
* self
[1] + tz
* self
[2];
122 self
[7] = tx
* self
[4] + ty
* self
[5] + tz
* self
[6];
123 self
[11] = tx
* self
[8] + ty
* self
[9] + tz
* self
[10];
128 // Triangle intersection using barycentric coord method
129 function Triangle(p1
, p2
, p3
) {
130 var edge1
= sub(p3
, p1
);
131 var edge2
= sub(p2
, p1
);
132 var normal
= cross(edge1
, edge2
);
133 if (Math
.abs(normal
[0]) > Math
.abs(normal
[1]))
134 if (Math
.abs(normal
[0]) > Math
.abs(normal
[2]))
139 if (Math
.abs(normal
[1]) > Math
.abs(normal
[2]))
143 var u
= (this.axis
+ 1) % 3;
144 var v
= (this.axis
+ 2) % 3;
150 this.normal
= normalise(normal
);
151 this.nu
= normal
[u
] / normal
[this.axis
];
152 this.nv
= normal
[v
] / normal
[this.axis
];
153 this.nd
= dot(normal
, p1
) / normal
[this.axis
];
154 var det
= u1
* v2
- v1
* u2
;
158 this.nv1
= -v1
/ det
;
160 this.nv2
= -u2
/ det
;
161 this.material
= [0.7, 0.7, 0.7];
164 Triangle
.prototype.intersect = function(orig
, dir
, near
, far
) {
165 var u
= (this.axis
+ 1) % 3;
166 var v
= (this.axis
+ 2) % 3;
167 var d
= dir
[this.axis
] + this.nu
* dir
[u
] + this.nv
* dir
[v
];
168 var t
= (this.nd
- orig
[this.axis
] - this.nu
* orig
[u
] - this.nv
* orig
[v
]) / d
;
169 if (t
< near
|| t
> far
)
171 var Pu
= orig
[u
] + t
* dir
[u
] - this.eu
;
172 var Pv
= orig
[v
] + t
* dir
[v
] - this.ev
;
173 var a2
= Pv
* this.nu1
+ Pu
* this.nv1
;
176 var a3
= Pu
* this.nu2
+ Pv
* this.nv2
;
185 function Scene(a_triangles
) {
186 this.triangles
= a_triangles
;
188 this.ambient
= [0,0,0];
189 this.background
= [0.8,0.8,1];
191 var zero
= new Array(0,0,0);
193 Scene
.prototype.intersect = function(origin
, dir
, near
, far
) {
195 for (i
= 0; i
< this.triangles
.length
; i
++) {
196 var triangle
= this.triangles
[i
];
197 var d
= triangle
.intersect(origin
, dir
, near
, far
);
198 if (d
== null || d
> far
|| d
< near
)
205 return [this.background
[0],this.background
[1],this.background
[2]];
207 var normal
= closest
.normal
;
208 var hit
= add(origin
, scale(dir
, far
));
209 if (dot(dir
, normal
) > 0)
210 normal
= [-normal
[0], -normal
[1], -normal
[2]];
213 if (closest
.shader
) {
214 colour
= closest
.shader(closest
, hit
, dir
);
216 colour
= closest
.material
;
220 var reflected
= null;
221 if (colour
.reflection
> 0.001) {
222 var reflection
= addVector(scale(normal
, -2*dot(dir
, normal
)), dir
);
223 reflected
= this.intersect(hit
, reflection
, 0.0001, 1000000);
224 if (colour
.reflection
>= 0.999999)
228 var l
= [this.ambient
[0], this.ambient
[1], this.ambient
[2]];
229 for (var i
= 0; i
< this.lights
.length
; i
++) {
230 var light
= this.lights
[i
];
231 var toLight
= sub(light
, hit
);
232 var distance
= lengthVector(toLight
);
233 scaleVector(toLight
, 1.0/distance
);
235 if (this.blocked(hit
, toLight
, distance
))
237 var nl
= dot(normal
, toLight
);
239 addVector(l
, scale(light
.colour
, nl
));
241 l
= scalev(l
, colour
);
243 l
= addVector(scaleVector(l
, 1 - colour
.reflection
), scaleVector(reflected
, colour
.reflection
));
248 Scene
.prototype.blocked = function(O
, D
, far
) {
251 for (i
= 0; i
< this.triangles
.length
; i
++) {
252 var triangle
= this.triangles
[i
];
253 var d
= triangle
.intersect(O
, D
, near
, far
);
254 if (d
== null || d
> far
|| d
< near
)
263 // this camera code is from notes i made ages ago, it is from *somewhere* -- i cannot remember where
265 function Camera(origin
, lookat
, up
) {
266 var zaxis
= normaliseVector(subVector(lookat
, origin
));
267 var xaxis
= normaliseVector(cross(up
, zaxis
));
268 var yaxis
= normaliseVector(cross(xaxis
, subVector([0,0,0], zaxis
)));
269 var m
= new Array(16);
270 m
[0] = xaxis
[0]; m
[1] = xaxis
[1]; m
[2] = xaxis
[2];
271 m
[4] = yaxis
[0]; m
[5] = yaxis
[1]; m
[6] = yaxis
[2];
272 m
[8] = zaxis
[0]; m
[9] = zaxis
[1]; m
[10] = zaxis
[2];
274 m
[3] = 0; m
[7] = 0; m
[11] = 0;
275 this.origin
= origin
;
276 this.directions
= new Array(4);
277 this.directions
[0] = normalise([-0.7, 0.7, 1]);
278 this.directions
[1] = normalise([ 0.7, 0.7, 1]);
279 this.directions
[2] = normalise([ 0.7, -0.7, 1]);
280 this.directions
[3] = normalise([-0.7, -0.7, 1]);
281 this.directions
[0] = transformMatrix(m
, this.directions
[0]);
282 this.directions
[1] = transformMatrix(m
, this.directions
[1]);
283 this.directions
[2] = transformMatrix(m
, this.directions
[2]);
284 this.directions
[3] = transformMatrix(m
, this.directions
[3]);
287 Camera
.prototype.generateRayPair = function(y
) {
288 rays
= new Array(new Object(), new Object());
289 rays
[0].origin
= this.origin
;
290 rays
[1].origin
= this.origin
;
291 rays
[0].dir
= addVector(scale(this.directions
[0], y
), scale(this.directions
[3], 1 - y
));
292 rays
[1].dir
= addVector(scale(this.directions
[1], y
), scale(this.directions
[2], 1 - y
));
296 function renderRows(camera
, scene
, pixels
, width
, height
, starty
, stopy
) {
297 for (var y
= starty
; y
< stopy
; y
++) {
298 var rays
= camera
.generateRayPair(y
/ height
);
299 for (var x
= 0; x
< width
; x
++) {
301 var origin
= addVector(scale(rays
[0].origin
, xp
), scale(rays
[1].origin
, 1 - xp
));
302 var dir
= normaliseVector(addVector(scale(rays
[0].dir
, xp
), scale(rays
[1].dir
, 1 - xp
)));
303 var l
= scene
.intersect(origin
, dir
);
309 Camera
.prototype.render = function(scene
, pixels
, width
, height
) {
312 renderRows(cam
, scene
, pixels
, width
, height
, 0, height
);
317 function raytraceScene(size
)
319 var startDate
= new Date().getTime();
320 var numTriangles
= 2 * 6;
321 var triangles
= new Array();//numTriangles);
322 var tfl
= createVector(-10, 10, -10);
323 var tfr
= createVector( 10, 10, -10);
324 var tbl
= createVector(-10, 10, 10);
325 var tbr
= createVector( 10, 10, 10);
326 var bfl
= createVector(-10, -10, -10);
327 var bfr
= createVector( 10, -10, -10);
328 var bbl
= createVector(-10, -10, 10);
329 var bbr
= createVector( 10, -10, 10);
335 triangles
[i
++] = new Triangle(tfl
, tfr
, bfr
);
336 triangles
[i
++] = new Triangle(tfl
, bfr
, bfl
);
338 triangles
[i
++] = new Triangle(tbl
, tbr
, bbr
);
339 triangles
[i
++] = new Triangle(tbl
, bbr
, bbl
);
340 // triangles[i-1].material = [0.7,0.2,0.2];
341 // triangles[i-1].material.reflection = 0.8;
343 triangles
[i
++] = new Triangle(tbl
, tfl
, bbl
);
344 // triangles[i-1].reflection = 0.6;
345 triangles
[i
++] = new Triangle(tfl
, bfl
, bbl
);
346 // triangles[i-1].reflection = 0.6;
348 triangles
[i
++] = new Triangle(tbr
, tfr
, bbr
);
349 triangles
[i
++] = new Triangle(tfr
, bfr
, bbr
);
351 triangles
[i
++] = new Triangle(tbl
, tbr
, tfr
);
352 triangles
[i
++] = new Triangle(tbl
, tfr
, tfl
);
354 triangles
[i
++] = new Triangle(bbl
, bbr
, bfr
);
355 triangles
[i
++] = new Triangle(bbl
, bfr
, bfl
);
358 var green
= createVector(0.0, 0.4, 0.0);
359 var grey
= createVector(0.4, 0.4, 0.4);
360 grey
.reflection
= 1.0;
361 var floorShader = function(tri
, pos
, view
) {
362 var x
= ((pos
[0]/32) % 2 + 2) % 2;
363 var z
= ((pos
[2]/32 + 0.3) % 2 + 2) % 2;
364 if (x
< 1 != z
< 1) {
365 //in the real world we use the fresnel term...
366 // var angle = 1-dot(view, tri.normal);
370 //grey.reflection = angle;
375 var ffl
= createVector(-1000, -30, -1000);
376 var ffr
= createVector( 1000, -30, -1000);
377 var fbl
= createVector(-1000, -30, 1000);
378 var fbr
= createVector( 1000, -30, 1000);
379 triangles
[i
++] = new Triangle(fbl
, fbr
, ffr
);
380 triangles
[i
-1].shader
= floorShader
;
381 triangles
[i
++] = new Triangle(fbl
, ffr
, ffl
);
382 triangles
[i
-1].shader
= floorShader
;
384 var _scene
= new Scene(triangles
);
385 _scene
.lights
[0] = createVector(20, 38, -22);
386 _scene
.lights
[0].colour
= createVector(0.7, 0.3, 0.3);
387 _scene
.lights
[1] = createVector(-23, 40, 17);
388 _scene
.lights
[1].colour
= createVector(0.7, 0.3, 0.3);
389 _scene
.lights
[2] = createVector(23, 20, 17);
390 _scene
.lights
[2].colour
= createVector(0.7, 0.7, 0.7);
391 _scene
.ambient
= createVector(0.1, 0.1, 0.1);
392 // _scene.background = createVector(0.7, 0.7, 1.0);
394 var pixels
= new Array();
395 for (var y
= 0; y
< size
; y
++) {
396 pixels
[y
] = new Array();
397 for (var x
= 0; x
< size
; x
++) {
402 var _camera
= new Camera(createVector(-40, 40, 40), createVector(0, 0, 0), createVector(0, 1, 0));
403 _camera
.render(_scene
, pixels
, size
, size
);
408 function arrayToCanvasCommands(pixels
, size
)
410 var s
= '<canvas id="renderCanvas" width="30px" height="30px"></canvas><scr' + 'ipt>\nvar pixels = [';
411 for (var y
= 0; y
< size
; y
++) {
413 for (var x
= 0; x
< size
; x
++) {
414 s
+= "[" + pixels
[y
][x
] + "],";
418 s
+= '];\n var canvas = document.getElementById("renderCanvas").getContext("2d");\n\
422 canvas.fillStyle = "red";\n\
423 canvas.fillRect(0, 0, size, size);\n\
424 canvas.scale(1, -1);\n\
425 canvas.translate(0, -size);\n\
427 if (!canvas.setFillColor)\n\
428 canvas.setFillColor = function(r, g, b, a) {\n\
429 this.fillStyle = "rgb("+[Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]+")";\n\
432 for (var y = 0; y < size; y++) {\n\
433 for (var x = 0; x < size; x++) {\n\
434 var l = pixels[y][x];\n\
435 canvas.setFillColor(l[0], l[1], l[2], 1);\n\
436 canvas.fillRect(x, y, 1, 1);\n\
443 window
.onload = function(){ startTest("sunspider-3d-raytrace", '8f9b64d5');
447 test("3D Raytrace", function(){
448 rayoutput
= raytraceScene(15);
451 test("Convert pixels to canvas", function(){
452 for ( var i
= 0; i
< 10; i
++ )
453 testOutput
= arrayToCanvasCommands(rayoutput
, 15);