added a point light to the scene
[eraytracer.git] / raytracer.erl
blobfa44729539056ed2b2b759f5c6387449a3aa98cd
1 -module(raytracer).
2 -compile(export_all).
4 -record(vector, {x, y, z}).
5 -record(colour, {r, g, b}).
6 -record(ray, {origin, direction}).
7 -record(screen, {width, height}). % screen dimensions in the 3D world
8 -record(camera, {location, rotation, fov, screen}).
9 -record(sphere, {radius, center, colour}).
10 -record(point_light, {colour, intensity, location}).
11 %-record(axis_aligned_cube, {size, location}).
12 -define(BACKGROUND_COLOUR, #colour{r=255, g=255, b=255}).
13 -define(ERROR_COLOUR, #colour{r=255, g=0, b=0}).
14 -define(UNKNOWN_COLOUR, #colour{r=0, g=255, b=0}).
15 -define(FOG_DISTANCE, 20).
17 raytraced_pixel_list(0, 0, _) ->
18 done;
19 raytraced_pixel_list(Width, Height, Scene) when Width > 0, Height > 0 ->
20 lists:flatmap(
21 fun(Y) ->
22 lists:map(
23 fun(X) ->
24 % coordinates passed as a percentage
25 trace_ray_from_pixel({X/Width, Y/Height}, Scene) end,
26 lists:seq(0, Width - 1)) end,
27 lists:seq(0, Height - 1)).
29 trace_ray_from_pixel({X, Y}, [Camera|Rest_of_scene]) ->
30 Ray = ray_through_pixel(X, Y, Camera),
31 case nearest_object_intersecting_ray(Ray, Rest_of_scene) of
32 {Nearest_object, _Distance} ->
33 %io:format("hit: ~w~n", [{Nearest_object, _Distance}]),
34 colour_to_pixel(object_colour(Nearest_object));
35 none ->
36 colour_to_pixel(?BACKGROUND_COLOUR);
37 _Else ->
38 colour_to_pixel(?ERROR_COLOUR)
39 end.
41 nearest_object_intersecting_ray(Ray, Scene) ->
42 nearest_object_intersecting_ray(Ray, none, infinity, Scene).
43 nearest_object_intersecting_ray(_Ray, _NearestObj, infinity, []) ->
44 none;
45 nearest_object_intersecting_ray(_Ray, NearestObj, Distance, []) ->
46 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
47 {NearestObj, Distance};
48 nearest_object_intersecting_ray(Ray,
49 NearestObj,
50 Distance,
51 [CurrentObject|Rest_of_scene]) ->
52 NewDistance = ray_object_intersect(Ray, CurrentObject),
53 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
54 if (NewDistance /= infinity)
55 and ((Distance == infinity) or (Distance > NewDistance)) ->
56 %io:format("another closer object found~n", []),
57 nearest_object_intersecting_ray(
58 Ray,
59 CurrentObject,
60 NewDistance,
61 Rest_of_scene);
62 true ->
63 %io:format("no closer obj found~n", []),
64 nearest_object_intersecting_ray(Ray,
65 NearestObj,
66 Distance,
67 Rest_of_scene)
68 end.
70 ray_object_intersect(Ray, Object) ->
71 case Object of
72 #sphere{} ->
73 ray_sphere_intersect(Ray, Object);
74 _Else ->
75 infinity
76 end.
78 ray_sphere_intersect(
79 #ray{origin=#vector{
80 x=X0, y=Y0, z=Z0},
81 direction=#vector{
82 x=Xd, y=Yd, z=Zd}},
83 #sphere{radius=Radius, center=#vector{
84 x=Xc, y=Yc, z=Zc}}) ->
85 A = Xd*Xd + Yd*Yd + Zd*Zd,
86 B = 2 * (Xd*(X0-Xc) + Yd*(Y0-Yc) + Zd*(Z0-Zc)),
87 C = (X0-Xc)*(X0-Xc) + (Y0-Yc)*(Y0-Yc) + (Z0-Zc)*(Z0-Zc) - Radius*Radius,
88 Discriminant = B*B - 4*A*C,
89 %io:format("A=~w B=~w C=~w discriminant=~w~n",
90 % [A, B, C, Discriminant]),
91 if Discriminant >= 0 ->
92 T0 = (-B + math:sqrt(Discriminant))/2,
93 T1 = (-B - math:sqrt(Discriminant))/2,
94 if (T0 >= 0) and (T1 >= 0) ->
95 %io:format("T0=~w T1=~w~n", [T0, T1]),
96 lists:min([T0, T1]);
97 true ->
98 infinity
99 end;
100 true ->
101 infinity
102 end.
106 focal_length(Angle, Dimension) ->
107 Dimension/(2*math:tan(Angle*(math:pi()/180)/2)).
109 point_on_screen(X, Y, Camera) ->
110 %TODO: implement rotation (using quaternions)
111 Screen_width = (Camera#camera.screen)#screen.width,
112 Screen_height = (Camera#camera.screen)#screen.height,
113 lists:foldl(fun(Vect, Sum) -> vector_add(Vect, Sum) end,
114 Camera#camera.location,
115 [vector_scalar_mult(
116 #vector{x=0, y=0, z=1},
117 focal_length(
118 Camera#camera.fov,
119 Screen_width)),
120 #vector{x = (X-0.5) * Screen_width,
121 y=0,
122 z=0},
123 #vector{x=0,
124 y= (Y-0.5) * Screen_height,
125 z=0}
129 shoot_ray(From, Through) ->
130 #ray{origin=From, direction=vector_sub(Through, From)}.
132 % assume that X and Y are percentages of the 3D world screen dimensions
133 ray_through_pixel(X, Y, Camera) ->
134 shoot_ray(Camera#camera.location, point_on_screen(X, Y, Camera)).
136 vectors_equal(V1, V2) ->
137 vectors_equal(V1, V2, 0.0001).
138 vectors_equal(V1, V2, Epsilon) ->
139 (V1#vector.x + Epsilon >= V2#vector.x)
140 and (V1#vector.x - Epsilon =<V2#vector.x)
141 and (V1#vector.y + Epsilon >= V2#vector.y)
142 and (V1#vector.y - Epsilon =<V2#vector.y)
143 and (V1#vector.z + Epsilon >= V2#vector.z)
144 and (V1#vector.z - Epsilon =<V2#vector.z).
147 vector_add(V1, V2) ->
148 #vector{x = V1#vector.x + V2#vector.x,
149 y = V1#vector.y + V2#vector.y,
150 z = V1#vector.z + V2#vector.z}.
152 vector_sub(V1, V2) ->
153 #vector{x = V1#vector.x - V2#vector.x,
154 y = V1#vector.y - V2#vector.y,
155 z = V1#vector.z - V2#vector.z}.
157 vector_square_mag(#vector{x=X, y=Y, z=Z}) ->
158 X*X + Y*Y + Z*Z.
160 vector_mag(V) ->
161 math:sqrt(vector_square_mag(V)).
163 vector_scalar_mult(#vector{x=X, y=Y, z=Z}, Scalar) ->
164 #vector{x=X*Scalar, y=Y*Scalar, z=Z*Scalar}.
166 vector_dot_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
167 A1*B1 + A2*B2 + A3*B3.
169 vector_cross_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
170 #vector{x = A2*B3 - A3*B2,
171 y = A3*B1 - A1*B3,
172 z = A1*B2 - A2*B1}.
174 vector_normalize(V) ->
175 Mag = vector_mag(V),
176 if Mag == 0 ->
177 #vector{x=0, y=0, z=0};
178 true ->
179 vector_scalar_mult(V, 1/vector_mag(V))
180 end.
182 vector_neg(#vector{x=X, y=Y, z=Z}) ->
183 #vector{x=-X, y=-Y, z=-Z}.
185 vector_rotate(V1, _V2) ->
186 %TODO: implement using quaternions
189 object_colour(#sphere{ colour=C}) ->
191 object_colour(_Unknown) ->
192 ?UNKNOWN_COLOUR.
194 colour_to_vector(#colour{r=R, g=G, b=B}) ->
195 #vector{x=R, y=G, z=B}.
196 vector_to_colour(#vector{x=X, y=Y, z=Z}) ->
197 #colour{r=X, g=Y, b=Z}.
198 colour_to_pixel(#colour{r=R, g=G, b=B}) ->
199 {R, G, B}.
201 % returns a list of objects in the scene
202 % camera is assumed to be the first element in the scene
203 scene() ->
204 [#camera{location=#vector{x=0, y=0, z=0},
205 rotation=#vector{x=0, y=0, z=0},
206 fov=90,
207 screen=#screen{width=4, height=3}},
208 #point_light{colour=#colour{r=255, g=255, b=128},
209 intensity=1,
210 location=#vector{x=5, y=5, z=1}},
211 #sphere{radius=4,
212 center=#vector{x=0, y=0, z=7},
213 colour=#colour{r=0, g=128, b=255}},
214 #sphere{radius=4,
215 center=#vector{x=-5, y=3, z=9},
216 colour=#colour{r=255, g=128, b=0}},
217 #sphere{radius=4,
218 center=#vector{x=-5, y=-2, z=10},
219 colour=#colour{r=128, g=255, b=0}}
223 % assumes Pixels are ordered in a row by row fasion
224 write_pixels_to_ppm(Width, Height, MaxValue, Pixels, Filename) ->
225 case file:open(Filename, write) of
226 {ok, IoDevice} ->
227 io:format("file opened~n", []),
228 io:format(IoDevice, "P3~n", []),
229 io:format(IoDevice, "~p ~p~n", [Width, Height]),
230 io:format(IoDevice, "~p~n", [MaxValue]),
231 lists:foreach(
232 fun({R, G, B}) ->
233 io:format(IoDevice, "~p ~p ~p ",
234 [R, G, B]) end,
235 Pixels),
236 file:close(IoDevice),
237 io:format("done~n", []);
238 error ->
239 io:format("error opening file~n", [])
240 end.
242 go() ->
243 go(16, 12, "/tmp/traced.ppm").
244 go(Width, Height, Filename) ->
245 write_pixels_to_ppm(Width,
246 Height,
247 255,
248 raytraced_pixel_list(Width,
249 Height,
250 scene()),
251 Filename).
253 % testing
254 scene_test() ->
255 io:format("testing the scene function", []),
256 case scene() of
257 [{camera,
258 {vector, 0, 0, 0},
259 {vector, 0, 0, 0},
261 {screen, 4, 3}},
262 {point_light,
263 {colour, 255, 255, 128},
265 {vector, 5, 5, 1}},
266 {sphere,
268 {vector, 0, 0, 7},
269 {colour, 0, 128, 255}},
270 {sphere,
272 {vector, -5, 3, 9},
273 {colour, 255, 128, 0}},
274 {sphere,
276 {vector, -5, -2, 10},
277 {colour, 128, 255, 0}}] ->
278 true;
279 _Else ->
280 false
281 end.
283 failing_test() ->
284 io:format("this test always fails", []),
285 false.
287 passing_test() ->
288 io:format("this test always passes", []),
289 true.
291 run_tests() ->
292 Tests = [fun scene_test/0,
293 fun passing_test/0,
294 fun vector_equality_test/0,
295 fun vector_addition_test/0,
296 fun vector_subtraction_test/0,
297 fun vector_square_mag_test/0,
298 fun vector_mag_test/0,
299 fun vector_scalar_multiplication_test/0,
300 fun vector_dot_product_test/0,
301 fun vector_cross_product_test/0,
302 fun vector_normalization_test/0,
303 fun vector_negation_test/0,
304 fun ray_through_pixel_test/0,
305 fun ray_shooting_test/0,
306 fun point_on_screen_test/0,
307 fun nearest_object_intersecting_ray_test/0,
308 fun focal_length_test/0,
309 fun vector_rotation_test/0
311 run_tests(Tests, 1, true).
313 run_tests([], _Num, Success) ->
314 case Success of
315 true ->
316 io:format("Success!~n", []),
318 _Else ->
319 io:format("some tests failed~n", []),
320 failed
321 end;
323 run_tests([First_test|Rest_of_tests], Num, Success_so_far) ->
324 io:format("test #~p: ", [Num]),
325 Current_success = First_test(),
326 case Current_success of
327 true ->
328 io:format(" - OK~n", []);
329 _Else ->
330 io:format(" - FAILED~n", [])
331 end,
332 run_tests(Rest_of_tests, Num + 1, Current_success and Success_so_far).
334 vector_equality_test() ->
335 io:format("vector equality"),
336 Vector1 = #vector{x=0, y=0, z=0},
337 Vector2 = #vector{x=1234, y=-234, z=0},
338 Vector3 = #vector{x=0.0983, y=0.0214, z=0.12342},
339 Vector4 = #vector{x=0.0984, y=0.0213, z=0.12341},
340 Vector5 = #vector{x=10/3, y=-10/6, z=8/7},
341 Vector6 = #vector{x=3.3, y=-1.6, z=1.1},
343 Subtest1 = vectors_equal(Vector1, Vector1)
344 and vectors_equal(Vector2, Vector2)
345 and not (vectors_equal(Vector1, Vector2))
346 and not (vectors_equal(Vector2, Vector1)),
347 Subtest2 = vectors_equal(Vector3, Vector4, 0.0001),
348 Subtest3 = vectors_equal(Vector5, Vector6, 0.1),
350 Subtest1 and Subtest2 and Subtest3.
353 vector_addition_test() ->
354 io:format("vector addition", []),
355 Vector0 = vector_add(
356 #vector{x=3, y=7, z=-3},
357 #vector{x=0, y=-24, z=123}),
358 Subtest1 = (Vector0#vector.x == 3)
359 and (Vector0#vector.y == -17)
360 and (Vector0#vector.z == 120),
362 Vector1 = #vector{x=5, y=0, z=984},
363 Vector2 = vector_add(Vector1, Vector1),
364 Subtest2 = (Vector2#vector.x == Vector1#vector.x*2)
365 and (Vector2#vector.y == Vector1#vector.y*2)
366 and (Vector2#vector.z == Vector1#vector.z*2),
368 Vector3 = #vector{x=908, y=-098, z=234},
369 Vector4 = vector_add(Vector3, #vector{x=0, y=0, z=0}),
370 Subtest3 = vectors_equal(Vector3, Vector4),
372 Subtest1 and Subtest2 and Subtest3.
374 vector_subtraction_test() ->
375 io:format("vector subtraction", []),
376 Vector1 = #vector{x=0, y=0, z=0},
377 Vector2 = #vector{x=8390, y=-2098, z=939},
378 Vector3 = #vector{x=1, y=1, z=1},
379 Vector4 = #vector{x=-1, y=-1, z=-1},
381 Subtest1 = vectors_equal(Vector1, vector_sub(Vector1, Vector1)),
382 Subtest2 = vectors_equal(Vector3, vector_sub(Vector3, Vector1)),
383 Subtest3 = not vectors_equal(Vector3, vector_sub(Vector1, Vector3)),
384 Subtest4 = vectors_equal(Vector4, vector_sub(Vector4, Vector1)),
385 Subtest5 = not vectors_equal(Vector4, vector_sub(Vector1, Vector4)),
386 Subtest5 = vectors_equal(vector_add(Vector2, Vector4),
387 vector_sub(Vector2, Vector3)),
389 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5.
391 vector_square_mag_test() ->
392 io:format("vector square magnitude test", []),
393 Vector1 = #vector{x=0, y=0, z=0},
394 Vector2 = #vector{x=1, y=1, z=1},
395 Vector3 = #vector{x=3, y=-4, z=0},
397 Subtest1 = (0 == vector_square_mag(Vector1)),
398 Subtest2 = (3 == vector_square_mag(Vector2)),
399 Subtest3 = (25 == vector_square_mag(Vector3)),
401 Subtest1 and Subtest2 and Subtest3.
403 vector_mag_test() ->
404 io:format("vector magnitude test", []),
405 Vector1 = #vector{x=0, y=0, z=0},
406 Vector2 = #vector{x=1, y=1, z=1},
407 Vector3 = #vector{x=3, y=-4, z=0},
409 Subtest1 = (0 == vector_mag(Vector1)),
410 Subtest2 = (math:sqrt(3) == vector_mag(Vector2)),
411 Subtest3 = (5 == vector_mag(Vector3)),
413 Subtest1 and Subtest2 and Subtest3.
415 vector_scalar_multiplication_test() ->
416 io:format("scalar multiplication test", []),
417 Vector1 = #vector{x=0, y=0, z=0},
418 Vector2 = #vector{x=1, y=1, z=1},
419 Vector3 = #vector{x=3, y=-4, z=0},
421 Subtest1 = vectors_equal(Vector1, vector_scalar_mult(Vector1, 45)),
422 Subtest2 = vectors_equal(Vector1, vector_scalar_mult(Vector1, -13)),
423 Subtest3 = vectors_equal(Vector1, vector_scalar_mult(Vector3, 0)),
424 Subtest4 = vectors_equal(#vector{x=4, y=4, z=4},
425 vector_scalar_mult(Vector2, 4)),
426 Subtest5 = vectors_equal(Vector3, vector_scalar_mult(Vector3, 1)),
427 Subtest6 = not vectors_equal(Vector3, vector_scalar_mult(Vector3, -3)),
429 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
431 vector_dot_product_test() ->
432 io:format("dot product test", []),
433 Vector1 = #vector{x=1, y=3, z=-5},
434 Vector2 = #vector{x=4, y=-2, z=-1},
435 Vector3 = #vector{x=0, y=0, z=0},
436 Vector4 = #vector{x=1, y=0, z=0},
437 Vector5 = #vector{x=0, y=1, z=0},
439 Subtest1 = 3 == vector_dot_product(Vector1, Vector2),
440 Subtest2 = vector_dot_product(Vector2, Vector2)
441 == vector_square_mag(Vector2),
442 Subtest3 = 0 == vector_dot_product(Vector3, Vector1),
443 Subtest4 = 0 == vector_dot_product(Vector4, Vector5),
445 Subtest1 and Subtest2 and Subtest3 and Subtest4.
447 vector_cross_product_test() ->
448 io:format("cross product test", []),
449 Vector1 = #vector{x=0, y=0, z=0},
450 Vector2 = #vector{x=1, y=0, z=0},
451 Vector3 = #vector{x=0, y=1, z=0},
452 Vector4 = #vector{x=0, y=0, z=1},
453 Vector5 = #vector{x=1, y=2, z=3},
454 Vector6 = #vector{x=4, y=5, z=6},
455 Vector7 = #vector{x=-3, y=6, z=-3},
456 Vector8 = #vector{x=-1, y=0, z=0},
457 Vector9 = #vector{x=-9, y=8, z=433},
459 Subtest1 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector2)),
460 Subtest2 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector8)),
461 Subtest3 = vectors_equal(Vector2, vector_cross_product(Vector3, Vector4)),
462 Subtest4 = vectors_equal(Vector7, vector_cross_product(Vector5, Vector6)),
463 Subtest5 = vectors_equal(
464 vector_cross_product(Vector7,
465 vector_add(Vector8, Vector9)),
466 vector_add(
467 vector_cross_product(Vector7, Vector8),
468 vector_cross_product(Vector7, Vector9))),
469 Subtest6 = vectors_equal(Vector1,
470 vector_add(
471 vector_add(
472 vector_cross_product(
473 Vector7,
474 vector_cross_product(Vector8, Vector9)),
475 vector_cross_product(
476 Vector8,
477 vector_cross_product(Vector9, Vector7))),
478 vector_cross_product(
479 Vector9,
480 vector_cross_product(Vector7, Vector8)))),
482 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
484 vector_normalization_test() ->
485 io:format("normalization test", []),
486 Vector1 = #vector{x=0, y=0, z=0},
487 Vector2 = #vector{x=1, y=0, z=0},
488 Vector3 = #vector{x=5, y=0, z=0},
490 Subtest1 = vectors_equal(Vector1, vector_normalize(Vector1)),
491 Subtest2 = vectors_equal(Vector2, vector_normalize(Vector2)),
492 Subtest3 = vectors_equal(Vector2, vector_normalize(Vector3)),
493 Subtest4 = vectors_equal(Vector2, vector_normalize(
494 vector_scalar_mult(Vector2, 324))),
496 Subtest1 and Subtest2 and Subtest3 and Subtest4.
498 vector_negation_test() ->
499 io:format("vector negation test", []),
500 Vector1 = #vector{x=0, y=0, z=0},
501 Vector2 = #vector{x=4, y=-5, z=6},
503 Subtest1 = vectors_equal(Vector1, vector_neg(Vector1)),
504 Subtest2 = vectors_equal(Vector2, vector_neg(vector_neg(Vector2))),
506 Subtest1 and Subtest2.
508 ray_through_pixel_test() ->
509 io:format("ray through pixel test", []),
510 false.
512 ray_shooting_test() ->
513 io:format("ray shooting test"),
514 Vector1 = #vector{x=0, y=0, z=0},
515 Vector2 = #vector{x=1, y=0, z=0},
517 Subtest1 = vectors_equal(
518 (shoot_ray(Vector1, Vector2))#ray.direction,
519 Vector2),
521 Subtest1.
523 ray_sphere_intersection_test() ->
524 Sphere = #sphere{
525 radius=3,
526 center=#vector{x = 0, y=0, z=10},
527 colour=#colour{r=111, g=111, b=111}},
528 Ray1 = #ray{
529 origin=#vector{x=0, y=0, z=0},
530 direction=#vector{x=0, y=0, z=1}},
531 Ray2 = #ray{
532 origin=#vector{x=3, y=0, z=0},
533 direction=#vector{x=0, y=0, z=1}},
534 Ray3 = #ray{
535 origin=#vector{x=4, y=0, z=0},
536 direction=#vector{x=0, y=0, z=1}},
537 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray1, Sphere)]),
538 Subtest1 = ray_sphere_intersect(Ray1, Sphere) == 7.0,
539 Subtest2 = ray_sphere_intersect(Ray2, Sphere) == 10.0,
540 Subtest3 = ray_sphere_intersect(Ray3, Sphere) == none,
541 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray2, Sphere)]),
542 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray3, Sphere)]),
543 Subtest1 and Subtest2 and Subtest3.
545 point_on_screen_test() ->
546 io:format("point on screen test", []),
547 Camera1 = #camera{location=#vector{x=0, y=0, z=0},
548 rotation=#vector{x=0, y=0, z=0},
549 fov=90,
550 screen=#screen{width=1, height=1}},
551 Camera2 = #camera{location=#vector{x=0, y=0, z=0},
552 rotation=#vector{x=0, y=0, z=0},
553 fov=90,
554 screen=#screen{width=640, height=480}},
556 Subtest1 = vectors_equal(
557 #vector{x=0, y=0, z=0.5},
558 point_on_screen(0.5, 0.5, Camera1)),
559 Subtest2 = vectors_equal(
560 #vector{x=-0.5, y=-0.5, z=0.5},
561 point_on_screen(0, 0, Camera1)),
562 Subtest3 = vectors_equal(
563 #vector{x=0.5, y=0.5, z=0.5},
564 point_on_screen(1, 1, Camera1)),
565 Subtest4 = vectors_equal(
566 point_on_screen(0, 0, Camera2),
567 #vector{x=-320, y=-240, z=320}),
568 Subtest5 = vectors_equal(
569 point_on_screen(1, 1, Camera2),
570 #vector{x=320, y=240, z=320}),
571 Subtest6 = vectors_equal(
572 point_on_screen(0.5, 0.5, Camera2),
573 #vector{x=0, y=0, z=320}),
575 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
577 nearest_object_intersecting_ray_test() ->
578 io:format("nearest object intersecting ray test", []),
579 % test to make sure that we really get the closest object
580 Sphere1=#sphere{radius=5,
581 center=#vector{x=0, y=0, z=10},
582 colour=#colour{r=0, g=0, b=10}},
583 Sphere2=#sphere{radius=5,
584 center=#vector{x=0, y=0, z=20},
585 colour=#colour{r=0, g=0, b=20}},
586 Sphere3=#sphere{radius=5,
587 center=#vector{x=0, y=0, z=30},
588 colour=#colour{r=0, g=0, b=30}},
589 Sphere4=#sphere{radius=5,
590 center=#vector{x=0, y=0, z=-10},
591 colour=#colour{r=0, g=0, b=-10}},
592 Scene1=[Sphere1, Sphere2, Sphere3, Sphere4],
593 Ray1=#ray{origin=#vector{x=0, y=0, z=0},
594 direction=#vector{x=0, y=0, z=1}},
596 Subtest1 = {Sphere1, 5} == nearest_object_intersecting_ray(Ray1, Scene1),
598 Subtest1.
600 focal_length_test() ->
601 Epsilon = 0.1,
602 Size = 36,
603 io:format("focal length test", []),
604 lists:foldl(
605 fun({Focal_length, Dimension}, Matches) ->
606 %Result = focal_length(Dimension, Size),
607 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
608 Matches
609 and ((Focal_length + Epsilon >= focal_length(
610 Dimension, Size))
611 and (Focal_length - Epsilon =< focal_length(
612 Dimension, Size)))
613 end, true,
614 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
616 vector_rotation_test() ->
617 io:format("vector rotation test", []),
618 Vector1 = #vector{x=0, y=0, z=0},
619 Vector2 = #vector{x=0, y=1, z=0},
620 Vector3 = #vector{x=90, y=0, z=0},
621 Vector4 = #vector{x=45, y=0, z=0},
622 Vector5 = #vector{x=30.11, y=-988.2, z=92.231},
623 Vector6 = #vector{x=0, y=0, z=1},
625 Subtest1 = vectors_equal(
626 vector_rotate(Vector1, Vector1),
627 Vector1),
628 Subtest2 = vectors_equal(
629 vector_rotate(Vector5, Vector1),
630 Vector5),
631 Subtest3 = vectors_equal(
632 vector_rotate(
633 vector_rotate(Vector5, Vector4),
634 Vector4),
635 vector_rotate(Vector5, Vector3)),
636 Subtest4 = vectors_equal(
637 Vector6,
638 vector_rotate(Vector2, Vector3)),
640 Subtest1 and Subtest2 and Subtest3 and Subtest4.