changed the background colour to black
[eraytracer.git] / raytracer.erl
blob59cd18ed9550883a232de931c9731af5a88959ca
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=0, g=0, b=0}).
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, 40).
18 raytraced_pixel_list(0, 0, _) ->
19 done;
20 raytraced_pixel_list(Width, Height, Scene) when Width > 0, Height > 0 ->
21 lists:flatmap(
22 fun(Y) ->
23 lists:map(
24 fun(X) ->
25 % coordinates passed as a percentage
26 colour_to_pixel(
27 colour_trunc(
28 trace_ray_from_pixel(
29 {X/Width, Y/Height}, Scene))) end,
30 lists:seq(0, Width - 1)) end,
31 lists:seq(0, Height - 1)).
33 trace_ray_from_pixel({X, Y}, [Camera|Rest_of_scene]) ->
34 Ray = ray_through_pixel(X, Y, Camera),
35 case nearest_object_intersecting_ray(Ray, Rest_of_scene) of
36 {Nearest_object, Distance} ->
37 %io:format("hit: ~w~n", [{Nearest_object, _Distance}]),
38 lighting_function(Nearest_object, Distance);
39 none ->
40 ?BACKGROUND_COLOUR;
41 _Else ->
42 ?ERROR_COLOUR
43 end.
45 lighting_function(Object, Distance) ->
46 if Distance =< ?FOG_DISTANCE ->
47 vector_to_colour(
48 vector_scalar_mult(
49 colour_to_vector(object_colour(Object)),
50 (?FOG_DISTANCE - Distance)/?FOG_DISTANCE));
51 true ->
52 #colour{r=0, g=0, b=0}
53 end.
56 nearest_object_intersecting_ray(Ray, Scene) ->
57 nearest_object_intersecting_ray(Ray, none, infinity, Scene).
58 nearest_object_intersecting_ray(_Ray, _NearestObj, infinity, []) ->
59 none;
60 nearest_object_intersecting_ray(_Ray, NearestObj, Distance, []) ->
61 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
62 {NearestObj, Distance};
63 nearest_object_intersecting_ray(Ray,
64 NearestObj,
65 Distance,
66 [CurrentObject|Rest_of_scene]) ->
67 NewDistance = ray_object_intersect(Ray, CurrentObject),
68 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
69 if (NewDistance /= infinity)
70 and ((Distance == infinity) or (Distance > NewDistance)) ->
71 %io:format("another closer object found~n", []),
72 nearest_object_intersecting_ray(
73 Ray,
74 CurrentObject,
75 NewDistance,
76 Rest_of_scene);
77 true ->
78 %io:format("no closer obj found~n", []),
79 nearest_object_intersecting_ray(Ray,
80 NearestObj,
81 Distance,
82 Rest_of_scene)
83 end.
85 ray_object_intersect(Ray, Object) ->
86 case Object of
87 #sphere{} ->
88 ray_sphere_intersect(Ray, Object);
89 _Else ->
90 infinity
91 end.
93 ray_sphere_intersect(
94 #ray{origin=#vector{
95 x=X0, y=Y0, z=Z0},
96 direction=#vector{
97 x=Xd, y=Yd, z=Zd}},
98 #sphere{radius=Radius, center=#vector{
99 x=Xc, y=Yc, z=Zc}}) ->
100 A = Xd*Xd + Yd*Yd + Zd*Zd,
101 B = 2 * (Xd*(X0-Xc) + Yd*(Y0-Yc) + Zd*(Z0-Zc)),
102 C = (X0-Xc)*(X0-Xc) + (Y0-Yc)*(Y0-Yc) + (Z0-Zc)*(Z0-Zc) - Radius*Radius,
103 Discriminant = B*B - 4*A*C,
104 %io:format("A=~w B=~w C=~w discriminant=~w~n",
105 % [A, B, C, Discriminant]),
106 if Discriminant >= 0 ->
107 T0 = (-B + math:sqrt(Discriminant))/2,
108 T1 = (-B - math:sqrt(Discriminant))/2,
109 if (T0 >= 0) and (T1 >= 0) ->
110 %io:format("T0=~w T1=~w~n", [T0, T1]),
111 lists:min([T0, T1]);
112 true ->
113 infinity
114 end;
115 true ->
116 infinity
117 end.
121 focal_length(Angle, Dimension) ->
122 Dimension/(2*math:tan(Angle*(math:pi()/180)/2)).
124 point_on_screen(X, Y, Camera) ->
125 %TODO: implement rotation (using quaternions)
126 Screen_width = (Camera#camera.screen)#screen.width,
127 Screen_height = (Camera#camera.screen)#screen.height,
128 lists:foldl(fun(Vect, Sum) -> vector_add(Vect, Sum) end,
129 Camera#camera.location,
130 [vector_scalar_mult(
131 #vector{x=0, y=0, z=1},
132 focal_length(
133 Camera#camera.fov,
134 Screen_width)),
135 #vector{x = (X-0.5) * Screen_width,
136 y=0,
137 z=0},
138 #vector{x=0,
139 y= (Y-0.5) * Screen_height,
140 z=0}
144 shoot_ray(From, Through) ->
145 #ray{origin=From, direction=vector_sub(Through, From)}.
147 % assume that X and Y are percentages of the 3D world screen dimensions
148 ray_through_pixel(X, Y, Camera) ->
149 shoot_ray(Camera#camera.location, point_on_screen(X, Y, Camera)).
151 vectors_equal(V1, V2) ->
152 vectors_equal(V1, V2, 0.0001).
153 vectors_equal(V1, V2, Epsilon) ->
154 (V1#vector.x + Epsilon >= V2#vector.x)
155 and (V1#vector.x - Epsilon =<V2#vector.x)
156 and (V1#vector.y + Epsilon >= V2#vector.y)
157 and (V1#vector.y - Epsilon =<V2#vector.y)
158 and (V1#vector.z + Epsilon >= V2#vector.z)
159 and (V1#vector.z - Epsilon =<V2#vector.z).
162 vector_add(V1, V2) ->
163 #vector{x = V1#vector.x + V2#vector.x,
164 y = V1#vector.y + V2#vector.y,
165 z = V1#vector.z + V2#vector.z}.
167 vector_sub(V1, V2) ->
168 #vector{x = V1#vector.x - V2#vector.x,
169 y = V1#vector.y - V2#vector.y,
170 z = V1#vector.z - V2#vector.z}.
172 vector_square_mag(#vector{x=X, y=Y, z=Z}) ->
173 X*X + Y*Y + Z*Z.
175 vector_mag(V) ->
176 math:sqrt(vector_square_mag(V)).
178 vector_scalar_mult(#vector{x=X, y=Y, z=Z}, Scalar) ->
179 #vector{x=X*Scalar, y=Y*Scalar, z=Z*Scalar}.
181 vector_dot_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
182 A1*B1 + A2*B2 + A3*B3.
184 vector_cross_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
185 #vector{x = A2*B3 - A3*B2,
186 y = A3*B1 - A1*B3,
187 z = A1*B2 - A2*B1}.
189 vector_normalize(V) ->
190 Mag = vector_mag(V),
191 if Mag == 0 ->
192 #vector{x=0, y=0, z=0};
193 true ->
194 vector_scalar_mult(V, 1/vector_mag(V))
195 end.
197 vector_neg(#vector{x=X, y=Y, z=Z}) ->
198 #vector{x=-X, y=-Y, z=-Z}.
200 vector_rotate(V1, _V2) ->
201 %TODO: implement using quaternions
204 object_colour(#sphere{ colour=C}) ->
206 object_colour(_Unknown) ->
207 ?UNKNOWN_COLOUR.
209 colour_to_vector(#colour{r=R, g=G, b=B}) ->
210 #vector{x=R, y=G, z=B}.
211 vector_to_colour(#vector{x=X, y=Y, z=Z}) ->
212 #colour{r=X, g=Y, b=Z}.
213 colour_to_pixel(#colour{r=R, g=G, b=B}) ->
214 {R, G, B}.
215 colour_trunc(#colour{r=R, g=G, b=B}) ->
216 #colour{r=trunc(R), g=trunc(G), b=trunc(B)}.
218 % returns a list of objects in the scene
219 % camera is assumed to be the first element in the scene
220 scene() ->
221 [#camera{location=#vector{x=0, y=0, z=0},
222 rotation=#vector{x=0, y=0, z=0},
223 fov=90,
224 screen=#screen{width=4, height=3}},
225 #point_light{colour=#colour{r=255, g=255, b=128},
226 intensity=1,
227 location=#vector{x=5, y=5, z=1}},
228 #sphere{radius=4,
229 center=#vector{x=0, y=0, z=7},
230 colour=#colour{r=0, g=128, b=255}},
231 #sphere{radius=4,
232 center=#vector{x=-5, y=3, z=9},
233 colour=#colour{r=255, g=128, b=0}},
234 #sphere{radius=4,
235 center=#vector{x=-5, y=-2, z=10},
236 colour=#colour{r=128, g=255, b=0}}
240 % assumes Pixels are ordered in a row by row fasion
241 write_pixels_to_ppm(Width, Height, MaxValue, Pixels, Filename) ->
242 case file:open(Filename, write) of
243 {ok, IoDevice} ->
244 io:format("file opened~n", []),
245 io:format(IoDevice, "P3~n", []),
246 io:format(IoDevice, "~p ~p~n", [Width, Height]),
247 io:format(IoDevice, "~p~n", [MaxValue]),
248 lists:foreach(
249 fun({R, G, B}) ->
250 io:format(IoDevice, "~p ~p ~p ",
251 [R, G, B]) end,
252 Pixels),
253 file:close(IoDevice),
254 io:format("done~n", []);
255 error ->
256 io:format("error opening file~n", [])
257 end.
259 go() ->
260 go(16, 12, "/tmp/traced.ppm").
261 go(Width, Height, Filename) ->
262 write_pixels_to_ppm(Width,
263 Height,
264 255,
265 raytraced_pixel_list(Width,
266 Height,
267 scene()),
268 Filename).
270 % testing
271 scene_test() ->
272 io:format("testing the scene function", []),
273 case scene() of
274 [{camera,
275 {vector, 0, 0, 0},
276 {vector, 0, 0, 0},
278 {screen, 4, 3}},
279 {point_light,
280 {colour, 255, 255, 128},
282 {vector, 5, 5, 1}},
283 {sphere,
285 {vector, 0, 0, 7},
286 {colour, 0, 128, 255}},
287 {sphere,
289 {vector, -5, 3, 9},
290 {colour, 255, 128, 0}},
291 {sphere,
293 {vector, -5, -2, 10},
294 {colour, 128, 255, 0}}] ->
295 true;
296 _Else ->
297 false
298 end.
300 failing_test() ->
301 io:format("this test always fails", []),
302 false.
304 passing_test() ->
305 io:format("this test always passes", []),
306 true.
308 run_tests() ->
309 Tests = [fun scene_test/0,
310 fun passing_test/0,
311 fun vector_equality_test/0,
312 fun vector_addition_test/0,
313 fun vector_subtraction_test/0,
314 fun vector_square_mag_test/0,
315 fun vector_mag_test/0,
316 fun vector_scalar_multiplication_test/0,
317 fun vector_dot_product_test/0,
318 fun vector_cross_product_test/0,
319 fun vector_normalization_test/0,
320 fun vector_negation_test/0,
321 fun ray_through_pixel_test/0,
322 fun ray_shooting_test/0,
323 fun point_on_screen_test/0,
324 fun nearest_object_intersecting_ray_test/0,
325 fun focal_length_test/0,
326 fun vector_rotation_test/0
328 run_tests(Tests, 1, true).
330 run_tests([], _Num, Success) ->
331 case Success of
332 true ->
333 io:format("Success!~n", []),
335 _Else ->
336 io:format("some tests failed~n", []),
337 failed
338 end;
340 run_tests([First_test|Rest_of_tests], Num, Success_so_far) ->
341 io:format("test #~p: ", [Num]),
342 Current_success = First_test(),
343 case Current_success of
344 true ->
345 io:format(" - OK~n", []);
346 _Else ->
347 io:format(" - FAILED~n", [])
348 end,
349 run_tests(Rest_of_tests, Num + 1, Current_success and Success_so_far).
351 vector_equality_test() ->
352 io:format("vector equality"),
353 Vector1 = #vector{x=0, y=0, z=0},
354 Vector2 = #vector{x=1234, y=-234, z=0},
355 Vector3 = #vector{x=0.0983, y=0.0214, z=0.12342},
356 Vector4 = #vector{x=0.0984, y=0.0213, z=0.12341},
357 Vector5 = #vector{x=10/3, y=-10/6, z=8/7},
358 Vector6 = #vector{x=3.3, y=-1.6, z=1.1},
360 Subtest1 = vectors_equal(Vector1, Vector1)
361 and vectors_equal(Vector2, Vector2)
362 and not (vectors_equal(Vector1, Vector2))
363 and not (vectors_equal(Vector2, Vector1)),
364 Subtest2 = vectors_equal(Vector3, Vector4, 0.0001),
365 Subtest3 = vectors_equal(Vector5, Vector6, 0.1),
367 Subtest1 and Subtest2 and Subtest3.
370 vector_addition_test() ->
371 io:format("vector addition", []),
372 Vector0 = vector_add(
373 #vector{x=3, y=7, z=-3},
374 #vector{x=0, y=-24, z=123}),
375 Subtest1 = (Vector0#vector.x == 3)
376 and (Vector0#vector.y == -17)
377 and (Vector0#vector.z == 120),
379 Vector1 = #vector{x=5, y=0, z=984},
380 Vector2 = vector_add(Vector1, Vector1),
381 Subtest2 = (Vector2#vector.x == Vector1#vector.x*2)
382 and (Vector2#vector.y == Vector1#vector.y*2)
383 and (Vector2#vector.z == Vector1#vector.z*2),
385 Vector3 = #vector{x=908, y=-098, z=234},
386 Vector4 = vector_add(Vector3, #vector{x=0, y=0, z=0}),
387 Subtest3 = vectors_equal(Vector3, Vector4),
389 Subtest1 and Subtest2 and Subtest3.
391 vector_subtraction_test() ->
392 io:format("vector subtraction", []),
393 Vector1 = #vector{x=0, y=0, z=0},
394 Vector2 = #vector{x=8390, y=-2098, z=939},
395 Vector3 = #vector{x=1, y=1, z=1},
396 Vector4 = #vector{x=-1, y=-1, z=-1},
398 Subtest1 = vectors_equal(Vector1, vector_sub(Vector1, Vector1)),
399 Subtest2 = vectors_equal(Vector3, vector_sub(Vector3, Vector1)),
400 Subtest3 = not vectors_equal(Vector3, vector_sub(Vector1, Vector3)),
401 Subtest4 = vectors_equal(Vector4, vector_sub(Vector4, Vector1)),
402 Subtest5 = not vectors_equal(Vector4, vector_sub(Vector1, Vector4)),
403 Subtest5 = vectors_equal(vector_add(Vector2, Vector4),
404 vector_sub(Vector2, Vector3)),
406 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5.
408 vector_square_mag_test() ->
409 io:format("vector square magnitude test", []),
410 Vector1 = #vector{x=0, y=0, z=0},
411 Vector2 = #vector{x=1, y=1, z=1},
412 Vector3 = #vector{x=3, y=-4, z=0},
414 Subtest1 = (0 == vector_square_mag(Vector1)),
415 Subtest2 = (3 == vector_square_mag(Vector2)),
416 Subtest3 = (25 == vector_square_mag(Vector3)),
418 Subtest1 and Subtest2 and Subtest3.
420 vector_mag_test() ->
421 io:format("vector magnitude test", []),
422 Vector1 = #vector{x=0, y=0, z=0},
423 Vector2 = #vector{x=1, y=1, z=1},
424 Vector3 = #vector{x=3, y=-4, z=0},
426 Subtest1 = (0 == vector_mag(Vector1)),
427 Subtest2 = (math:sqrt(3) == vector_mag(Vector2)),
428 Subtest3 = (5 == vector_mag(Vector3)),
430 Subtest1 and Subtest2 and Subtest3.
432 vector_scalar_multiplication_test() ->
433 io:format("scalar multiplication test", []),
434 Vector1 = #vector{x=0, y=0, z=0},
435 Vector2 = #vector{x=1, y=1, z=1},
436 Vector3 = #vector{x=3, y=-4, z=0},
438 Subtest1 = vectors_equal(Vector1, vector_scalar_mult(Vector1, 45)),
439 Subtest2 = vectors_equal(Vector1, vector_scalar_mult(Vector1, -13)),
440 Subtest3 = vectors_equal(Vector1, vector_scalar_mult(Vector3, 0)),
441 Subtest4 = vectors_equal(#vector{x=4, y=4, z=4},
442 vector_scalar_mult(Vector2, 4)),
443 Subtest5 = vectors_equal(Vector3, vector_scalar_mult(Vector3, 1)),
444 Subtest6 = not vectors_equal(Vector3, vector_scalar_mult(Vector3, -3)),
446 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
448 vector_dot_product_test() ->
449 io:format("dot product test", []),
450 Vector1 = #vector{x=1, y=3, z=-5},
451 Vector2 = #vector{x=4, y=-2, z=-1},
452 Vector3 = #vector{x=0, y=0, z=0},
453 Vector4 = #vector{x=1, y=0, z=0},
454 Vector5 = #vector{x=0, y=1, z=0},
456 Subtest1 = 3 == vector_dot_product(Vector1, Vector2),
457 Subtest2 = vector_dot_product(Vector2, Vector2)
458 == vector_square_mag(Vector2),
459 Subtest3 = 0 == vector_dot_product(Vector3, Vector1),
460 Subtest4 = 0 == vector_dot_product(Vector4, Vector5),
462 Subtest1 and Subtest2 and Subtest3 and Subtest4.
464 vector_cross_product_test() ->
465 io:format("cross product test", []),
466 Vector1 = #vector{x=0, y=0, z=0},
467 Vector2 = #vector{x=1, y=0, z=0},
468 Vector3 = #vector{x=0, y=1, z=0},
469 Vector4 = #vector{x=0, y=0, z=1},
470 Vector5 = #vector{x=1, y=2, z=3},
471 Vector6 = #vector{x=4, y=5, z=6},
472 Vector7 = #vector{x=-3, y=6, z=-3},
473 Vector8 = #vector{x=-1, y=0, z=0},
474 Vector9 = #vector{x=-9, y=8, z=433},
476 Subtest1 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector2)),
477 Subtest2 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector8)),
478 Subtest3 = vectors_equal(Vector2, vector_cross_product(Vector3, Vector4)),
479 Subtest4 = vectors_equal(Vector7, vector_cross_product(Vector5, Vector6)),
480 Subtest5 = vectors_equal(
481 vector_cross_product(Vector7,
482 vector_add(Vector8, Vector9)),
483 vector_add(
484 vector_cross_product(Vector7, Vector8),
485 vector_cross_product(Vector7, Vector9))),
486 Subtest6 = vectors_equal(Vector1,
487 vector_add(
488 vector_add(
489 vector_cross_product(
490 Vector7,
491 vector_cross_product(Vector8, Vector9)),
492 vector_cross_product(
493 Vector8,
494 vector_cross_product(Vector9, Vector7))),
495 vector_cross_product(
496 Vector9,
497 vector_cross_product(Vector7, Vector8)))),
499 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
501 vector_normalization_test() ->
502 io:format("normalization test", []),
503 Vector1 = #vector{x=0, y=0, z=0},
504 Vector2 = #vector{x=1, y=0, z=0},
505 Vector3 = #vector{x=5, y=0, z=0},
507 Subtest1 = vectors_equal(Vector1, vector_normalize(Vector1)),
508 Subtest2 = vectors_equal(Vector2, vector_normalize(Vector2)),
509 Subtest3 = vectors_equal(Vector2, vector_normalize(Vector3)),
510 Subtest4 = vectors_equal(Vector2, vector_normalize(
511 vector_scalar_mult(Vector2, 324))),
513 Subtest1 and Subtest2 and Subtest3 and Subtest4.
515 vector_negation_test() ->
516 io:format("vector negation test", []),
517 Vector1 = #vector{x=0, y=0, z=0},
518 Vector2 = #vector{x=4, y=-5, z=6},
520 Subtest1 = vectors_equal(Vector1, vector_neg(Vector1)),
521 Subtest2 = vectors_equal(Vector2, vector_neg(vector_neg(Vector2))),
523 Subtest1 and Subtest2.
525 ray_through_pixel_test() ->
526 io:format("ray through pixel test", []),
527 false.
529 ray_shooting_test() ->
530 io:format("ray shooting test"),
531 Vector1 = #vector{x=0, y=0, z=0},
532 Vector2 = #vector{x=1, y=0, z=0},
534 Subtest1 = vectors_equal(
535 (shoot_ray(Vector1, Vector2))#ray.direction,
536 Vector2),
538 Subtest1.
540 ray_sphere_intersection_test() ->
541 Sphere = #sphere{
542 radius=3,
543 center=#vector{x = 0, y=0, z=10},
544 colour=#colour{r=111, g=111, b=111}},
545 Ray1 = #ray{
546 origin=#vector{x=0, y=0, z=0},
547 direction=#vector{x=0, y=0, z=1}},
548 Ray2 = #ray{
549 origin=#vector{x=3, y=0, z=0},
550 direction=#vector{x=0, y=0, z=1}},
551 Ray3 = #ray{
552 origin=#vector{x=4, y=0, z=0},
553 direction=#vector{x=0, y=0, z=1}},
554 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray1, Sphere)]),
555 Subtest1 = ray_sphere_intersect(Ray1, Sphere) == 7.0,
556 Subtest2 = ray_sphere_intersect(Ray2, Sphere) == 10.0,
557 Subtest3 = ray_sphere_intersect(Ray3, Sphere) == none,
558 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray2, Sphere)]),
559 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray3, Sphere)]),
560 Subtest1 and Subtest2 and Subtest3.
562 point_on_screen_test() ->
563 io:format("point on screen test", []),
564 Camera1 = #camera{location=#vector{x=0, y=0, z=0},
565 rotation=#vector{x=0, y=0, z=0},
566 fov=90,
567 screen=#screen{width=1, height=1}},
568 Camera2 = #camera{location=#vector{x=0, y=0, z=0},
569 rotation=#vector{x=0, y=0, z=0},
570 fov=90,
571 screen=#screen{width=640, height=480}},
573 Subtest1 = vectors_equal(
574 #vector{x=0, y=0, z=0.5},
575 point_on_screen(0.5, 0.5, Camera1)),
576 Subtest2 = vectors_equal(
577 #vector{x=-0.5, y=-0.5, z=0.5},
578 point_on_screen(0, 0, Camera1)),
579 Subtest3 = vectors_equal(
580 #vector{x=0.5, y=0.5, z=0.5},
581 point_on_screen(1, 1, Camera1)),
582 Subtest4 = vectors_equal(
583 point_on_screen(0, 0, Camera2),
584 #vector{x=-320, y=-240, z=320}),
585 Subtest5 = vectors_equal(
586 point_on_screen(1, 1, Camera2),
587 #vector{x=320, y=240, z=320}),
588 Subtest6 = vectors_equal(
589 point_on_screen(0.5, 0.5, Camera2),
590 #vector{x=0, y=0, z=320}),
592 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
594 nearest_object_intersecting_ray_test() ->
595 io:format("nearest object intersecting ray test", []),
596 % test to make sure that we really get the closest object
597 Sphere1=#sphere{radius=5,
598 center=#vector{x=0, y=0, z=10},
599 colour=#colour{r=0, g=0, b=10}},
600 Sphere2=#sphere{radius=5,
601 center=#vector{x=0, y=0, z=20},
602 colour=#colour{r=0, g=0, b=20}},
603 Sphere3=#sphere{radius=5,
604 center=#vector{x=0, y=0, z=30},
605 colour=#colour{r=0, g=0, b=30}},
606 Sphere4=#sphere{radius=5,
607 center=#vector{x=0, y=0, z=-10},
608 colour=#colour{r=0, g=0, b=-10}},
609 Scene1=[Sphere1, Sphere2, Sphere3, Sphere4],
610 Ray1=#ray{origin=#vector{x=0, y=0, z=0},
611 direction=#vector{x=0, y=0, z=1}},
613 Subtest1 = {Sphere1, 5} == nearest_object_intersecting_ray(Ray1, Scene1),
615 Subtest1.
617 focal_length_test() ->
618 Epsilon = 0.1,
619 Size = 36,
620 io:format("focal length test", []),
621 lists:foldl(
622 fun({Focal_length, Dimension}, Matches) ->
623 %Result = focal_length(Dimension, Size),
624 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
625 Matches
626 and ((Focal_length + Epsilon >= focal_length(
627 Dimension, Size))
628 and (Focal_length - Epsilon =< focal_length(
629 Dimension, Size)))
630 end, true,
631 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
633 vector_rotation_test() ->
634 io:format("vector rotation test", []),
635 Vector1 = #vector{x=0, y=0, z=0},
636 Vector2 = #vector{x=0, y=1, z=0},
637 Vector3 = #vector{x=90, y=0, z=0},
638 Vector4 = #vector{x=45, y=0, z=0},
639 Vector5 = #vector{x=30.11, y=-988.2, z=92.231},
640 Vector6 = #vector{x=0, y=0, z=1},
642 Subtest1 = vectors_equal(
643 vector_rotate(Vector1, Vector1),
644 Vector1),
645 Subtest2 = vectors_equal(
646 vector_rotate(Vector5, Vector1),
647 Vector5),
648 Subtest3 = vectors_equal(
649 vector_rotate(
650 vector_rotate(Vector5, Vector4),
651 Vector4),
652 vector_rotate(Vector5, Vector3)),
653 Subtest4 = vectors_equal(
654 Vector6,
655 vector_rotate(Vector2, Vector3)),
657 Subtest1 and Subtest2 and Subtest3 and Subtest4.