OLD | NEW |
| (Empty) |
1 // The ray tracer code in this file is written by Adam Burmister. It | |
2 // is available in its original form from: | |
3 // | |
4 // http://labs.flog.nz.co/raytracer/ | |
5 // | |
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. | |
10 | |
11 var RayTrace = new BenchmarkSuite('RayTrace', 739989, [ | |
12 new Benchmark('RayTrace', renderScene) | |
13 ]); | |
14 | |
15 | |
16 // Variable used to hold a number that can be used to verify that | |
17 // the scene was ray traced correctly. | |
18 var checkNumber; | |
19 | |
20 | |
21 // ------------------------------------------------------------------------ | |
22 // ------------------------------------------------------------------------ | |
23 | |
24 // The following is a copy of parts of the Prototype JavaScript library: | |
25 | |
26 // Prototype JavaScript framework, version 1.5.0 | |
27 // (c) 2005-2007 Sam Stephenson | |
28 // | |
29 // Prototype is freely distributable under the terms of an MIT-style license. | |
30 // For details, see the Prototype web site: http://prototype.conio.net/ | |
31 | |
32 | |
33 var Class = { | |
34 create: function() { | |
35 return function() { | |
36 this.initialize.apply(this, arguments); | |
37 } | |
38 } | |
39 }; | |
40 | |
41 | |
42 Object.extend = function(destination, source) { | |
43 for (var property in source) { | |
44 destination[property] = source[property]; | |
45 } | |
46 return destination; | |
47 }; | |
48 | |
49 | |
50 // ------------------------------------------------------------------------ | |
51 // ------------------------------------------------------------------------ | |
52 | |
53 // The rest of this file is the actual ray tracer written by Adam | |
54 // Burmister. It's a concatenation of the following files: | |
55 // | |
56 // flog/color.js | |
57 // flog/light.js | |
58 // flog/vector.js | |
59 // flog/ray.js | |
60 // flog/scene.js | |
61 // flog/material/basematerial.js | |
62 // flog/material/solid.js | |
63 // flog/material/chessboard.js | |
64 // flog/shape/baseshape.js | |
65 // flog/shape/sphere.js | |
66 // flog/shape/plane.js | |
67 // flog/intersectioninfo.js | |
68 // flog/camera.js | |
69 // flog/background.js | |
70 // flog/engine.js | |
71 | |
72 | |
73 /* Fake a Flog.* namespace */ | |
74 if(typeof(Flog) == 'undefined') var Flog = {}; | |
75 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
76 | |
77 Flog.RayTracer.Color = Class.create(); | |
78 | |
79 Flog.RayTracer.Color.prototype = { | |
80 red : 0.0, | |
81 green : 0.0, | |
82 blue : 0.0, | |
83 | |
84 initialize : function(r, g, b) { | |
85 if(!r) r = 0.0; | |
86 if(!g) g = 0.0; | |
87 if(!b) b = 0.0; | |
88 | |
89 this.red = r; | |
90 this.green = g; | |
91 this.blue = b; | |
92 }, | |
93 | |
94 add : function(c1, c2){ | |
95 var result = new Flog.RayTracer.Color(0,0,0); | |
96 | |
97 result.red = c1.red + c2.red; | |
98 result.green = c1.green + c2.green; | |
99 result.blue = c1.blue + c2.blue; | |
100 | |
101 return result; | |
102 }, | |
103 | |
104 addScalar: function(c1, s){ | |
105 var result = new Flog.RayTracer.Color(0,0,0); | |
106 | |
107 result.red = c1.red + s; | |
108 result.green = c1.green + s; | |
109 result.blue = c1.blue + s; | |
110 | |
111 result.limit(); | |
112 | |
113 return result; | |
114 }, | |
115 | |
116 subtract: function(c1, c2){ | |
117 var result = new Flog.RayTracer.Color(0,0,0); | |
118 | |
119 result.red = c1.red - c2.red; | |
120 result.green = c1.green - c2.green; | |
121 result.blue = c1.blue - c2.blue; | |
122 | |
123 return result; | |
124 }, | |
125 | |
126 multiply : function(c1, c2) { | |
127 var result = new Flog.RayTracer.Color(0,0,0); | |
128 | |
129 result.red = c1.red * c2.red; | |
130 result.green = c1.green * c2.green; | |
131 result.blue = c1.blue * c2.blue; | |
132 | |
133 return result; | |
134 }, | |
135 | |
136 multiplyScalar : function(c1, f) { | |
137 var result = new Flog.RayTracer.Color(0,0,0); | |
138 | |
139 result.red = c1.red * f; | |
140 result.green = c1.green * f; | |
141 result.blue = c1.blue * f; | |
142 | |
143 return result; | |
144 }, | |
145 | |
146 divideFactor : function(c1, f) { | |
147 var result = new Flog.RayTracer.Color(0,0,0); | |
148 | |
149 result.red = c1.red / f; | |
150 result.green = c1.green / f; | |
151 result.blue = c1.blue / f; | |
152 | |
153 return result; | |
154 }, | |
155 | |
156 limit: function(){ | |
157 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.
0; | |
158 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.gree
n ) : 0.0; | |
159 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue )
: 0.0; | |
160 }, | |
161 | |
162 distance : function(color) { | |
163 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.gre
en) + Math.abs(this.blue - color.blue); | |
164 return d; | |
165 }, | |
166 | |
167 blend: function(c1, c2, w){ | |
168 var result = new Flog.RayTracer.Color(0,0,0); | |
169 result = Flog.RayTracer.Color.prototype.add( | |
170 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w), | |
171 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w) | |
172 ); | |
173 return result; | |
174 }, | |
175 | |
176 brightness : function() { | |
177 var r = Math.floor(this.red*255); | |
178 var g = Math.floor(this.green*255); | |
179 var b = Math.floor(this.blue*255); | |
180 return (r * 77 + g * 150 + b * 29) >> 8; | |
181 }, | |
182 | |
183 toString : function () { | |
184 var r = Math.floor(this.red*255); | |
185 var g = Math.floor(this.green*255); | |
186 var b = Math.floor(this.blue*255); | |
187 | |
188 return "rgb("+ r +","+ g +","+ b +")"; | |
189 } | |
190 } | |
191 /* Fake a Flog.* namespace */ | |
192 if(typeof(Flog) == 'undefined') var Flog = {}; | |
193 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
194 | |
195 Flog.RayTracer.Light = Class.create(); | |
196 | |
197 Flog.RayTracer.Light.prototype = { | |
198 position: null, | |
199 color: null, | |
200 intensity: 10.0, | |
201 | |
202 initialize : function(pos, color, intensity) { | |
203 this.position = pos; | |
204 this.color = color; | |
205 this.intensity = (intensity ? intensity : 10.0); | |
206 }, | |
207 | |
208 toString : function () { | |
209 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.
position.z + ']'; | |
210 } | |
211 } | |
212 /* Fake a Flog.* namespace */ | |
213 if(typeof(Flog) == 'undefined') var Flog = {}; | |
214 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
215 | |
216 Flog.RayTracer.Vector = Class.create(); | |
217 | |
218 Flog.RayTracer.Vector.prototype = { | |
219 x : 0.0, | |
220 y : 0.0, | |
221 z : 0.0, | |
222 | |
223 initialize : function(x, y, z) { | |
224 this.x = (x ? x : 0); | |
225 this.y = (y ? y : 0); | |
226 this.z = (z ? z : 0); | |
227 }, | |
228 | |
229 copy: function(vector){ | |
230 this.x = vector.x; | |
231 this.y = vector.y; | |
232 this.z = vector.z; | |
233 }, | |
234 | |
235 normalize : function() { | |
236 var m = this.magnitude(); | |
237 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m); | |
238 }, | |
239 | |
240 magnitude : function() { | |
241 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.
z)); | |
242 }, | |
243 | |
244 cross : function(w) { | |
245 return new Flog.RayTracer.Vector( | |
246 -this.z * w.y + this.y * w.z, | |
247 this.z * w.x - this.x * w.z, | |
248 -this.y * w.x + this.x * w.y); | |
249 }, | |
250 | |
251 dot : function(w) { | |
252 return this.x * w.x + this.y * w.y + this.z * w.z; | |
253 }, | |
254 | |
255 add : function(v, w) { | |
256 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z); | |
257 }, | |
258 | |
259 subtract : function(v, w) { | |
260 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']'; | |
261 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z); | |
262 }, | |
263 | |
264 multiplyVector : function(v, w) { | |
265 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z); | |
266 }, | |
267 | |
268 multiplyScalar : function(v, w) { | |
269 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w); | |
270 }, | |
271 | |
272 toString : function () { | |
273 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']'; | |
274 } | |
275 } | |
276 /* Fake a Flog.* namespace */ | |
277 if(typeof(Flog) == 'undefined') var Flog = {}; | |
278 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
279 | |
280 Flog.RayTracer.Ray = Class.create(); | |
281 | |
282 Flog.RayTracer.Ray.prototype = { | |
283 position : null, | |
284 direction : null, | |
285 initialize : function(pos, dir) { | |
286 this.position = pos; | |
287 this.direction = dir; | |
288 }, | |
289 | |
290 toString : function () { | |
291 return 'Ray [' + this.position + ',' + this.direction + ']'; | |
292 } | |
293 } | |
294 /* Fake a Flog.* namespace */ | |
295 if(typeof(Flog) == 'undefined') var Flog = {}; | |
296 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
297 | |
298 Flog.RayTracer.Scene = Class.create(); | |
299 | |
300 Flog.RayTracer.Scene.prototype = { | |
301 camera : null, | |
302 shapes : [], | |
303 lights : [], | |
304 background : null, | |
305 | |
306 initialize : function() { | |
307 this.camera = new Flog.RayTracer.Camera( | |
308 new Flog.RayTracer.Vector(0,0,-5), | |
309 new Flog.RayTracer.Vector(0,0,1), | |
310 new Flog.RayTracer.Vector(0,1,0) | |
311 ); | |
312 this.shapes = new Array(); | |
313 this.lights = new Array(); | |
314 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color
(0,0,0.5), 0.2); | |
315 } | |
316 } | |
317 /* Fake a Flog.* namespace */ | |
318 if(typeof(Flog) == 'undefined') var Flog = {}; | |
319 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
320 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {}; | |
321 | |
322 Flog.RayTracer.Material.BaseMaterial = Class.create(); | |
323 | |
324 Flog.RayTracer.Material.BaseMaterial.prototype = { | |
325 | |
326 gloss: 2.0, // [0...infinity] 0 = matt | |
327 transparency: 0.0, // 0=opaque | |
328 reflection: 0.0, // [0...infinity] 0 = no reflection | |
329 refraction: 0.50, | |
330 hasTexture: false, | |
331 | |
332 initialize : function() { | |
333 | |
334 }, | |
335 | |
336 getColor: function(u, v){ | |
337 | |
338 }, | |
339 | |
340 wrapUp: function(t){ | |
341 t = t % 2.0; | |
342 if(t < -1) t += 2.0; | |
343 if(t >= 1) t -= 2.0; | |
344 return t; | |
345 }, | |
346 | |
347 toString : function () { | |
348 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transp
arency + ', hasTexture=' + this.hasTexture +']'; | |
349 } | |
350 } | |
351 /* Fake a Flog.* namespace */ | |
352 if(typeof(Flog) == 'undefined') var Flog = {}; | |
353 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
354 | |
355 Flog.RayTracer.Material.Solid = Class.create(); | |
356 | |
357 Flog.RayTracer.Material.Solid.prototype = Object.extend( | |
358 new Flog.RayTracer.Material.BaseMaterial(), { | |
359 initialize : function(color, reflection, refraction, transparency, gloss
) { | |
360 this.color = color; | |
361 this.reflection = reflection; | |
362 this.transparency = transparency; | |
363 this.gloss = gloss; | |
364 this.hasTexture = false; | |
365 }, | |
366 | |
367 getColor: function(u, v){ | |
368 return this.color; | |
369 }, | |
370 | |
371 toString : function () { | |
372 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + th
is.transparency + ', hasTexture=' + this.hasTexture +']'; | |
373 } | |
374 } | |
375 ); | |
376 /* Fake a Flog.* namespace */ | |
377 if(typeof(Flog) == 'undefined') var Flog = {}; | |
378 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
379 | |
380 Flog.RayTracer.Material.Chessboard = Class.create(); | |
381 | |
382 Flog.RayTracer.Material.Chessboard.prototype = Object.extend( | |
383 new Flog.RayTracer.Material.BaseMaterial(), { | |
384 colorEven: null, | |
385 colorOdd: null, | |
386 density: 0.5, | |
387 | |
388 initialize : function(colorEven, colorOdd, reflection, transparency, glo
ss, density) { | |
389 this.colorEven = colorEven; | |
390 this.colorOdd = colorOdd; | |
391 this.reflection = reflection; | |
392 this.transparency = transparency; | |
393 this.gloss = gloss; | |
394 this.density = density; | |
395 this.hasTexture = true; | |
396 }, | |
397 | |
398 getColor: function(u, v){ | |
399 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density
); | |
400 | |
401 if(t < 0.0) | |
402 return this.colorEven; | |
403 else | |
404 return this.colorOdd; | |
405 }, | |
406 | |
407 toString : function () { | |
408 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + th
is.transparency + ', hasTexture=' + this.hasTexture +']'; | |
409 } | |
410 } | |
411 ); | |
412 /* Fake a Flog.* namespace */ | |
413 if(typeof(Flog) == 'undefined') var Flog = {}; | |
414 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
415 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; | |
416 | |
417 Flog.RayTracer.Shape.Sphere = Class.create(); | |
418 | |
419 Flog.RayTracer.Shape.Sphere.prototype = { | |
420 initialize : function(pos, radius, material) { | |
421 this.radius = radius; | |
422 this.position = pos; | |
423 this.material = material; | |
424 }, | |
425 | |
426 intersect: function(ray){ | |
427 var info = new Flog.RayTracer.IntersectionInfo(); | |
428 info.shape = this; | |
429 | |
430 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.po
sition); | |
431 | |
432 var B = dst.dot(ray.direction); | |
433 var C = dst.dot(dst) - (this.radius * this.radius); | |
434 var D = (B * B) - C; | |
435 | |
436 if(D > 0){ // intersection! | |
437 info.isHit = true; | |
438 info.distance = (-B) - Math.sqrt(D); | |
439 info.position = Flog.RayTracer.Vector.prototype.add( | |
440 ray.position, | |
441 Flog.RayTracer.Vector.prototype.
multiplyScalar( | |
442 ray.direction, | |
443 info.distance | |
444 ) | |
445 ); | |
446 info.normal = Flog.RayTracer.Vector.prototype.subtract( | |
447 info.position, | |
448 this.position | |
449 ).normalize(); | |
450 | |
451 info.color = this.material.getColor(0,0); | |
452 } else { | |
453 info.isHit = false; | |
454 } | |
455 return info; | |
456 }, | |
457 | |
458 toString : function () { | |
459 return 'Sphere [position=' + this.position + ', radius=' + this.radius +
']'; | |
460 } | |
461 } | |
462 /* Fake a Flog.* namespace */ | |
463 if(typeof(Flog) == 'undefined') var Flog = {}; | |
464 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
465 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; | |
466 | |
467 Flog.RayTracer.Shape.Plane = Class.create(); | |
468 | |
469 Flog.RayTracer.Shape.Plane.prototype = { | |
470 d: 0.0, | |
471 | |
472 initialize : function(pos, d, material) { | |
473 this.position = pos; | |
474 this.d = d; | |
475 this.material = material; | |
476 }, | |
477 | |
478 intersect: function(ray){ | |
479 var info = new Flog.RayTracer.IntersectionInfo(); | |
480 | |
481 var Vd = this.position.dot(ray.direction); | |
482 if(Vd == 0) return info; // no intersection | |
483 | |
484 var t = -(this.position.dot(ray.position) + this.d) / Vd; | |
485 if(t <= 0) return info; | |
486 | |
487 info.shape = this; | |
488 info.isHit = true; | |
489 info.position = Flog.RayTracer.Vector.prototype.add( | |
490 ray.position, | |
491 Flog.RayTracer.Vector.prototype.mult
iplyScalar( | |
492 ray.direction, | |
493 t | |
494 ) | |
495 ); | |
496 info.normal = this.position; | |
497 info.distance = t; | |
498 | |
499 if(this.material.hasTexture){ | |
500 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z,
-this.position.x); | |
501 var vV = vU.cross(this.position); | |
502 var u = info.position.dot(vU); | |
503 var v = info.position.dot(vV); | |
504 info.color = this.material.getColor(u,v); | |
505 } else { | |
506 info.color = this.material.getColor(0,0); | |
507 } | |
508 | |
509 return info; | |
510 }, | |
511 | |
512 toString : function () { | |
513 return 'Plane [' + this.position + ', d=' + this.d + ']'; | |
514 } | |
515 } | |
516 /* Fake a Flog.* namespace */ | |
517 if(typeof(Flog) == 'undefined') var Flog = {}; | |
518 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
519 | |
520 Flog.RayTracer.IntersectionInfo = Class.create(); | |
521 | |
522 Flog.RayTracer.IntersectionInfo.prototype = { | |
523 isHit: false, | |
524 hitCount: 0, | |
525 shape: null, | |
526 position: null, | |
527 normal: null, | |
528 color: null, | |
529 distance: null, | |
530 | |
531 initialize : function() { | |
532 this.color = new Flog.RayTracer.Color(0,0,0); | |
533 }, | |
534 | |
535 toString : function () { | |
536 return 'Intersection [' + this.position + ']'; | |
537 } | |
538 } | |
539 /* Fake a Flog.* namespace */ | |
540 if(typeof(Flog) == 'undefined') var Flog = {}; | |
541 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
542 | |
543 Flog.RayTracer.Camera = Class.create(); | |
544 | |
545 Flog.RayTracer.Camera.prototype = { | |
546 position: null, | |
547 lookAt: null, | |
548 equator: null, | |
549 up: null, | |
550 screen: null, | |
551 | |
552 initialize : function(pos, lookAt, up) { | |
553 this.position = pos; | |
554 this.lookAt = lookAt; | |
555 this.up = up; | |
556 this.equator = lookAt.normalize().cross(this.up); | |
557 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lo
okAt); | |
558 }, | |
559 | |
560 getRay: function(vx, vy){ | |
561 var pos = Flog.RayTracer.Vector.prototype.subtract( | |
562 this.screen, | |
563 Flog.RayTracer.Vector.prototype.subtract( | |
564 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx)
, | |
565 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy) | |
566 ) | |
567 ); | |
568 pos.y = pos.y * -1; | |
569 var dir = Flog.RayTracer.Vector.prototype.subtract( | |
570 pos, | |
571 this.position | |
572 ); | |
573 | |
574 var ray = new Flog.RayTracer.Ray(pos, dir.normalize()); | |
575 | |
576 return ray; | |
577 }, | |
578 | |
579 toString : function () { | |
580 return 'Ray []'; | |
581 } | |
582 } | |
583 /* Fake a Flog.* namespace */ | |
584 if(typeof(Flog) == 'undefined') var Flog = {}; | |
585 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
586 | |
587 Flog.RayTracer.Background = Class.create(); | |
588 | |
589 Flog.RayTracer.Background.prototype = { | |
590 color : null, | |
591 ambience : 0.0, | |
592 | |
593 initialize : function(color, ambience) { | |
594 this.color = color; | |
595 this.ambience = ambience; | |
596 } | |
597 } | |
598 /* Fake a Flog.* namespace */ | |
599 if(typeof(Flog) == 'undefined') var Flog = {}; | |
600 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; | |
601 | |
602 Flog.RayTracer.Engine = Class.create(); | |
603 | |
604 Flog.RayTracer.Engine.prototype = { | |
605 canvas: null, /* 2d context we can render to */ | |
606 | |
607 initialize: function(options){ | |
608 this.options = Object.extend({ | |
609 canvasHeight: 100, | |
610 canvasWidth: 100, | |
611 pixelWidth: 2, | |
612 pixelHeight: 2, | |
613 renderDiffuse: false, | |
614 renderShadows: false, | |
615 renderHighlights: false, | |
616 renderReflections: false, | |
617 rayDepth: 2 | |
618 }, options || {}); | |
619 | |
620 this.options.canvasHeight /= this.options.pixelHeight; | |
621 this.options.canvasWidth /= this.options.pixelWidth; | |
622 | |
623 /* TODO: dynamically include other scripts */ | |
624 }, | |
625 | |
626 setPixel: function(x, y, color){ | |
627 var pxW, pxH; | |
628 pxW = this.options.pixelWidth; | |
629 pxH = this.options.pixelHeight; | |
630 | |
631 if (this.canvas) { | |
632 this.canvas.fillStyle = color.toString(); | |
633 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH); | |
634 } else { | |
635 if (x === y) { | |
636 checkNumber += color.brightness(); | |
637 } | |
638 // print(x * pxW, y * pxH, pxW, pxH); | |
639 } | |
640 }, | |
641 | |
642 renderScene: function(scene, canvas){ | |
643 checkNumber = 0; | |
644 /* Get canvas */ | |
645 if (canvas) { | |
646 this.canvas = canvas.getContext("2d"); | |
647 } else { | |
648 this.canvas = null; | |
649 } | |
650 | |
651 var canvasHeight = this.options.canvasHeight; | |
652 var canvasWidth = this.options.canvasWidth; | |
653 | |
654 for(var y=0; y < canvasHeight; y++){ | |
655 for(var x=0; x < canvasWidth; x++){ | |
656 var yp = y * 1.0 / canvasHeight * 2 - 1; | |
657 var xp = x * 1.0 / canvasWidth * 2 - 1; | |
658 | |
659 var ray = scene.camera.getRay(xp, yp); | |
660 | |
661 var color = this.getPixelColor(ray, scene); | |
662 | |
663 this.setPixel(x, y, color); | |
664 } | |
665 } | |
666 if (checkNumber !== 2321) { | |
667 throw new Error("Scene rendered incorrectly"); | |
668 } | |
669 }, | |
670 | |
671 getPixelColor: function(ray, scene){ | |
672 var info = this.testIntersection(ray, scene, null); | |
673 if(info.isHit){ | |
674 var color = this.rayTrace(info, ray, scene, 0); | |
675 return color; | |
676 } | |
677 return scene.background.color; | |
678 }, | |
679 | |
680 testIntersection: function(ray, scene, exclude){ | |
681 var hits = 0; | |
682 var best = new Flog.RayTracer.IntersectionInfo(); | |
683 best.distance = 2000; | |
684 | |
685 for(var i=0; i<scene.shapes.length; i++){ | |
686 var shape = scene.shapes[i]; | |
687 | |
688 if(shape != exclude){ | |
689 var info = shape.intersect(ray); | |
690 if(info.isHit && info.distance >= 0 && info.distance < best.dist
ance){ | |
691 best = info; | |
692 hits++; | |
693 } | |
694 } | |
695 } | |
696 best.hitCount = hits; | |
697 return best; | |
698 }, | |
699 | |
700 getReflectionRay: function(P,N,V){ | |
701 var c1 = -N.dot(V); | |
702 var R1 = Flog.RayTracer.Vector.prototype.add( | |
703 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1), | |
704 V | |
705 ); | |
706 return new Flog.RayTracer.Ray(P, R1); | |
707 }, | |
708 | |
709 rayTrace: function(info, ray, scene, depth){ | |
710 // Calc ambient | |
711 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, sc
ene.background.ambience); | |
712 var oldColor = color; | |
713 var shininess = Math.pow(10, info.shape.material.gloss + 1); | |
714 | |
715 for(var i=0; i<scene.lights.length; i++){ | |
716 var light = scene.lights[i]; | |
717 | |
718 // Calc diffuse lighting | |
719 var v = Flog.RayTracer.Vector.prototype.subtract( | |
720 light.position, | |
721 info.position | |
722 ).normalize(); | |
723 | |
724 if(this.options.renderDiffuse){ | |
725 var L = v.dot(info.normal); | |
726 if(L > 0.0){ | |
727 color = Flog.RayTracer.Color.prototype.add( | |
728 color, | |
729 Flog.RayTracer.Color.prototype.multiply( | |
730 info.color, | |
731 Flog.RayTracer.Color.prototype.multi
plyScalar( | |
732 light.color, | |
733 L | |
734 ) | |
735 ) | |
736 ); | |
737 } | |
738 } | |
739 | |
740 // The greater the depth the more accurate the colours, but | |
741 // this is exponentially (!) expensive | |
742 if(depth <= this.options.rayDepth){ | |
743 // calculate reflection ray | |
744 if(this.options.renderReflections && info.shape.material.reflection >
0) | |
745 { | |
746 var reflectionRay = this.getReflectionRay(info.position, info.norm
al, ray.direction); | |
747 var refl = this.testIntersection(reflectionRay, scene, info.shape)
; | |
748 | |
749 if (refl.isHit && refl.distance > 0){ | |
750 refl.color = this.rayTrace(refl, reflectionRay, scene, depth +
1); | |
751 } else { | |
752 refl.color = scene.background.color; | |
753 } | |
754 | |
755 color = Flog.RayTracer.Color.prototype.blend( | |
756 color, | |
757 refl.color, | |
758 info.shape.material.reflection | |
759 ); | |
760 } | |
761 | |
762 // Refraction | |
763 /* TODO */ | |
764 } | |
765 | |
766 /* Render shadows and highlights */ | |
767 | |
768 var shadowInfo = new Flog.RayTracer.IntersectionInfo(); | |
769 | |
770 if(this.options.renderShadows){ | |
771 var shadowRay = new Flog.RayTracer.Ray(info.position, v); | |
772 | |
773 shadowInfo = this.testIntersection(shadowRay, scene, info.shape)
; | |
774 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shado
wInfo.shape.type != 'PLANE'*/){ | |
775 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color
, 0.5); | |
776 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transpare
ncy, 0.5)); | |
777 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB); | |
778 } | |
779 } | |
780 | |
781 // Phong specular highlights | |
782 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.materi
al.gloss > 0){ | |
783 var Lv = Flog.RayTracer.Vector.prototype.subtract( | |
784 info.shape.position, | |
785 light.position | |
786 ).normalize(); | |
787 | |
788 var E = Flog.RayTracer.Vector.prototype.subtract( | |
789 scene.camera.position, | |
790 info.shape.position | |
791 ).normalize(); | |
792 | |
793 var H = Flog.RayTracer.Vector.prototype.subtract( | |
794 E, | |
795 Lv | |
796 ).normalize(); | |
797 | |
798 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess); | |
799 color = Flog.RayTracer.Color.prototype.add( | |
800 Flog.RayTracer.Color.prototype.multiplyScalar(light.
color, glossWeight), | |
801 color | |
802 ); | |
803 } | |
804 } | |
805 color.limit(); | |
806 return color; | |
807 } | |
808 }; | |
809 | |
810 | |
811 function renderScene(){ | |
812 var scene = new Flog.RayTracer.Scene(); | |
813 | |
814 scene.camera = new Flog.RayTracer.Camera( | |
815 new Flog.RayTracer.Vector(0, 0, -15), | |
816 new Flog.RayTracer.Vector(-0.2, 0, 5), | |
817 new Flog.RayTracer.Vector(0, 1, 0) | |
818 ); | |
819 | |
820 scene.background = new Flog.RayTracer.Background( | |
821 new Flog.RayTracer.Color(0.5, 0.5, 0.5), | |
822 0.4 | |
823 ); | |
824 | |
825 var sphere = new Flog.RayTracer.Shape.Sphere( | |
826 new Flog.RayTracer.Vector(-1.5, 1.5, 2), | |
827 1.5, | |
828 new Flog.RayTracer.Material.Solid( | |
829 new Flog.RayTracer.Color(0,0.5,0.5), | |
830 0.3, | |
831 0.0, | |
832 0.0, | |
833 2.0 | |
834 ) | |
835 ); | |
836 | |
837 var sphere1 = new Flog.RayTracer.Shape.Sphere( | |
838 new Flog.RayTracer.Vector(1, 0.25, 1), | |
839 0.5, | |
840 new Flog.RayTracer.Material.Solid( | |
841 new Flog.RayTracer.Color(0.9,0.9,0.9), | |
842 0.1, | |
843 0.0, | |
844 0.0, | |
845 1.5 | |
846 ) | |
847 ); | |
848 | |
849 var plane = new Flog.RayTracer.Shape.Plane( | |
850 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normal
ize(), | |
851 1.2, | |
852 new Flog.RayTracer.Material.Chessboard( | |
853 new Flog.RayTracer.Color(1,1,1), | |
854 new Flog.RayTracer.Color(0,0,0), | |
855 0.2, | |
856 0.0, | |
857 1.0, | |
858 0.7 | |
859 ) | |
860 ); | |
861 | |
862 scene.shapes.push(plane); | |
863 scene.shapes.push(sphere); | |
864 scene.shapes.push(sphere1); | |
865 | |
866 var light = new Flog.RayTracer.Light( | |
867 new Flog.RayTracer.Vector(5, 10, -1), | |
868 new Flog.RayTracer.Color(0.8, 0.8, 0.8) | |
869 ); | |
870 | |
871 var light1 = new Flog.RayTracer.Light( | |
872 new Flog.RayTracer.Vector(-3, 5, -15), | |
873 new Flog.RayTracer.Color(0.8, 0.8, 0.8), | |
874 100 | |
875 ); | |
876 | |
877 scene.lights.push(light); | |
878 scene.lights.push(light1); | |
879 | |
880 var imageWidth = 100; // $F('imageWidth'); | |
881 var imageHeight = 100; // $F('imageHeight'); | |
882 var pixelSize = "5,5".split(','); // $F('pixelSize').split(','); | |
883 var renderDiffuse = true; // $F('renderDiffuse'); | |
884 var renderShadows = true; // $F('renderShadows'); | |
885 var renderHighlights = true; // $F('renderHighlights'); | |
886 var renderReflections = true; // $F('renderReflections'); | |
887 var rayDepth = 2;//$F('rayDepth'); | |
888 | |
889 var raytracer = new Flog.RayTracer.Engine( | |
890 { | |
891 canvasWidth: imageWidth, | |
892 canvasHeight: imageHeight, | |
893 pixelWidth: pixelSize[0], | |
894 pixelHeight: pixelSize[1], | |
895 "renderDiffuse": renderDiffuse, | |
896 "renderHighlights": renderHighlights, | |
897 "renderShadows": renderShadows, | |
898 "renderReflections": renderReflections, | |
899 "rayDepth": rayDepth | |
900 } | |
901 ); | |
902 | |
903 raytracer.renderScene(scene, null, 0); | |
904 } | |
OLD | NEW |