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