stopped exporting all functions
[eraytracer.git] / raytracer.erl
blob4b0b641a562b2bf343cf44c7f18dd1aa840fe8a4
2 %% raytracer.erl
4 %% a simple raytracer written in Erlang
6 %% Copyright (c) 2008 Michael Ploujnikov
8 %% This program is free software: you can redistribute it and/or modify
9 %% it under the terms of the GNU General Public License as published by
10 %% the Free Software Foundation, either version 2 of the License, or
11 %% (at your option) any later version.
13 %% This program is distributed in the hope that it will be useful,
14 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
15 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 %% GNU General Public License for more details.
18 %% You should have received a copy of the GNU General Public License
19 %% along with this program. If not, see <http://www.gnu.org/licenses/>.
21 %% Features:
22 %% * two object types:
23 %% * spheres
24 %% * triangles (not done)
25 %% * point lights
26 %% * shadows (not done)
27 %% * lighting based on local illumination models
28 %% * ambient (not done)
29 %% * diffuse
30 %% * specular
31 %% * attenuation (not done)
32 %% * reflections to a fixed depth
33 %% * PPM output file format
34 %% * randomly generated scene (not done)
35 %% * useful test suite (working but not very friendly when fails)
36 %% * concurrent (utilizes all CPUs in a single computer) (not done)
37 %% * distributed (across multiple computers) (not done)
41 -module(raytracer).
42 -export([go/0, go/4, run_tests/0]).
44 -record(vector, {x, y, z}).
45 -record(colour, {r, g, b}).
46 -record(ray, {origin, direction}).
47 -record(screen, {width, height}). % screen dimensions in the 3D world
48 -record(camera, {location, rotation, fov, screen}).
49 -record(material, {colour, specular_power, shininess, reflectivity}).
50 -record(sphere, {radius, center, material}).
51 -record(triangle, {v1, v2, v3, material}).
52 -record(point_light, {diffuse_colour, location, specular_colour}).
53 -define(BACKGROUND_COLOUR, #colour{r=0, g=0, b=0}).
54 -define(ERROR_COLOUR, #colour{r=1, g=0, b=0}).
55 -define(UNKNOWN_COLOUR, #colour{r=0, g=1, b=0}).
56 -define(FOG_DISTANCE, 40).
59 raytraced_pixel_list(0, 0, _, _) ->
60 done;
61 raytraced_pixel_list(Width, Height, Scene, Recursion_depth)
62 when Width > 0, Height > 0 ->
63 lists:flatmap(
64 fun(Y) ->
65 lists:map(
66 fun(X) ->
67 % coordinates passed as a percentage
68 colour_to_pixel(
69 trace_ray_from_pixel(
70 {X/Width, Y/Height}, Scene, Recursion_depth)) end,
71 lists:seq(0, Width - 1)) end,
72 lists:seq(0, Height - 1)).
74 % assumes X and Y are percentages of the screen dimensions
75 trace_ray_from_pixel({X, Y}, [Camera|Rest_of_scene], Recursion_depth) ->
76 pixel_colour_from_ray(ray_through_pixel(X, Y, Camera), Rest_of_scene,
77 Recursion_depth).
79 pixel_colour_from_ray(_Ray, _Scene, 0) ->
80 #colour{r=0, g=0, b=0};
81 pixel_colour_from_ray(Ray, Scene, Recursion_depth) ->
82 case nearest_object_intersecting_ray(Ray, Scene) of
83 {Nearest_object, _Distance, Hit_location, Hit_normal} ->
84 %io:format("hit: ~w~n", [{Nearest_object, _Distance}]),
86 vector_to_colour(lighting_function(Ray,
87 Nearest_object,
88 Hit_location,
89 Hit_normal,
90 Scene,
91 Recursion_depth));
92 none ->
93 ?BACKGROUND_COLOUR;
94 _Else ->
95 ?ERROR_COLOUR
96 end.
98 lighting_function(Ray, Object, Hit_location, Hit_normal, Scene,
99 Recursion_depth) ->
100 lists:foldl(
101 fun (#point_light{diffuse_colour=Light_colour,
102 location=Light_location,
103 specular_colour=Specular_colour},
104 Final_colour) ->
105 vector_add(
106 vector_scalar_mult(
107 colour_to_vector(
108 pixel_colour_from_ray(
109 #ray{origin=Hit_location,
110 direction=vector_reflect_about_normal(
111 vector_neg(Ray#ray.direction), Hit_normal)},
112 Scene,
113 Recursion_depth-1)),
114 object_reflectivity(Object)),
115 vector_add(
116 vector_component_mult(
117 colour_to_vector(Light_colour),
118 vector_add(
119 diffuse_term(Object,
120 Light_location,
121 Hit_location,
122 Hit_normal),
123 specular_term(Ray#ray.direction,
124 Light_location,
125 Hit_location,
126 Hit_normal,
127 object_specular_power(Object),
128 object_shininess(Object),
129 Specular_colour))),
130 Final_colour));
131 (_Not_a_point_light, Final_colour) ->
132 Final_colour
133 end,
134 #vector{x=0, y=0, z=0},
135 Scene).
137 diffuse_term(Object, Light_location, Hit_location, Hit_normal) ->
138 vector_scalar_mult(
139 colour_to_vector(object_diffuse_colour(Object)),
140 lists:max([0,
141 vector_dot_product(Hit_normal,
142 vector_normalize(
143 vector_sub(Light_location,
144 Hit_location)))])).
146 specular_term(EyeVector, Light_location, Hit_location, Hit_normal,
147 Specular_power, Shininess, Specular_colour) ->
148 vector_scalar_mult(
149 colour_to_vector(Specular_colour),
150 Shininess*math:pow(
151 lists:max([0,
152 vector_dot_product(
153 vector_normalize(
154 vector_add(
155 vector_normalize(
156 vector_sub(Light_location, Hit_location)),
157 vector_neg(EyeVector))),
158 Hit_normal)]), Specular_power)).
160 nearest_object_intersecting_ray(Ray, Scene) ->
161 nearest_object_intersecting_ray(
162 Ray, none, hitlocation, hitnormal, infinity, Scene).
163 nearest_object_intersecting_ray(
164 _Ray, _NearestObj, _Hit_location, _Normal, infinity, []) ->
165 none;
166 nearest_object_intersecting_ray(
167 _Ray, NearestObj, Hit_location, Normal, Distance, []) ->
168 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
169 {NearestObj, Distance, Hit_location, Normal};
170 nearest_object_intersecting_ray(Ray,
171 NearestObj,
172 Hit_location,
173 Normal,
174 Distance,
175 [CurrentObject|Rest_of_scene]) ->
176 NewDistance = ray_object_intersect(Ray, CurrentObject),
177 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
178 if (NewDistance /= infinity)
179 and ((Distance == infinity) or (Distance > NewDistance)) ->
180 %io:format("another closer object found~n", []),
181 New_hit_location =
182 vector_add(Ray#ray.origin,
183 vector_scalar_mult(Ray#ray.direction, NewDistance)),
184 New_normal = object_normal_at_point(
185 CurrentObject, New_hit_location),
186 nearest_object_intersecting_ray(
187 Ray,
188 CurrentObject,
189 New_hit_location,
190 New_normal,
191 NewDistance,
192 Rest_of_scene);
193 true ->
194 %io:format("no closer obj found~n", []),
195 nearest_object_intersecting_ray(Ray,
196 NearestObj,
197 Hit_location,
198 Normal,
199 Distance,
200 Rest_of_scene)
201 end.
203 ray_object_intersect(Ray, Object) ->
204 case Object of
205 #sphere{} ->
206 ray_sphere_intersect(Ray, Object);
207 #triangle{} ->
208 ray_triangle_intersect(Ray, Object);
209 _Else ->
210 infinity
211 end.
213 object_normal_at_point(#sphere{center=Center}, Point) ->
214 vector_normalize(
215 vector_sub(Point, Center)).
217 ray_sphere_intersect(
218 #ray{origin=#vector{
219 x=X0, y=Y0, z=Z0},
220 direction=#vector{
221 x=Xd, y=Yd, z=Zd}},
222 #sphere{radius=Radius, center=#vector{
223 x=Xc, y=Yc, z=Zc}}) ->
224 Epsilon = 0.001,
225 A = Xd*Xd + Yd*Yd + Zd*Zd,
226 B = 2 * (Xd*(X0-Xc) + Yd*(Y0-Yc) + Zd*(Z0-Zc)),
227 C = (X0-Xc)*(X0-Xc) + (Y0-Yc)*(Y0-Yc) + (Z0-Zc)*(Z0-Zc) - Radius*Radius,
228 Discriminant = B*B - 4*A*C,
229 %io:format("A=~w B=~w C=~w discriminant=~w~n",
230 % [A, B, C, Discriminant]),
231 if Discriminant >= 0 ->
232 T0 = (-B + math:sqrt(Discriminant))/2,
233 T1 = (-B - math:sqrt(Discriminant))/2,
234 if (T0 >= 0) and (T1 >= 0) ->
235 %io:format("T0=~w T1=~w~n", [T0, T1]),
236 lists:min([T0, T1]);
237 true ->
238 infinity
239 end;
240 true ->
241 infinity
242 end.
244 ray_triangle_intersect(Ray, Triangle) ->
245 infinity.
247 focal_length(Angle, Dimension) ->
248 Dimension/(2*math:tan(Angle*(math:pi()/180)/2)).
250 point_on_screen(X, Y, Camera) ->
251 %TODO: implement rotation (using quaternions)
252 Screen_width = (Camera#camera.screen)#screen.width,
253 Screen_height = (Camera#camera.screen)#screen.height,
254 lists:foldl(fun(Vect, Sum) -> vector_add(Vect, Sum) end,
255 Camera#camera.location,
256 [vector_scalar_mult(
257 #vector{x=0, y=0, z=1},
258 focal_length(
259 Camera#camera.fov,
260 Screen_width)),
261 #vector{x = (X-0.5) * Screen_width,
262 y=0,
263 z=0},
264 #vector{x=0,
265 y= (Y-0.5) * Screen_height,
266 z=0}
270 shoot_ray(From, Through) ->
271 #ray{origin=From, direction=vector_normalize(vector_sub(Through, From))}.
273 % assume that X and Y are percentages of the 3D world screen dimensions
274 ray_through_pixel(X, Y, Camera) ->
275 shoot_ray(Camera#camera.location, point_on_screen(X, Y, Camera)).
277 vectors_equal(V1, V2) ->
278 vectors_equal(V1, V2, 0.0001).
279 vectors_equal(V1, V2, Epsilon) ->
280 (V1#vector.x + Epsilon >= V2#vector.x)
281 and (V1#vector.x - Epsilon =<V2#vector.x)
282 and (V1#vector.y + Epsilon >= V2#vector.y)
283 and (V1#vector.y - Epsilon =<V2#vector.y)
284 and (V1#vector.z + Epsilon >= V2#vector.z)
285 and (V1#vector.z - Epsilon =<V2#vector.z).
288 vector_add(V1, V2) ->
289 #vector{x = V1#vector.x + V2#vector.x,
290 y = V1#vector.y + V2#vector.y,
291 z = V1#vector.z + V2#vector.z}.
293 vector_sub(V1, V2) ->
294 #vector{x = V1#vector.x - V2#vector.x,
295 y = V1#vector.y - V2#vector.y,
296 z = V1#vector.z - V2#vector.z}.
298 vector_square_mag(#vector{x=X, y=Y, z=Z}) ->
299 X*X + Y*Y + Z*Z.
301 vector_mag(V) ->
302 math:sqrt(vector_square_mag(V)).
304 vector_scalar_mult(#vector{x=X, y=Y, z=Z}, Scalar) ->
305 #vector{x=X*Scalar, y=Y*Scalar, z=Z*Scalar}.
307 vector_component_mult(#vector{x=X1, y=Y1, z=Z1}, #vector{x=X2, y=Y2, z=Z2}) ->
308 #vector{x=X1*X2, y=Y1*Y2, z=Z1*Z2}.
310 vector_dot_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
311 A1*B1 + A2*B2 + A3*B3.
313 vector_cross_product(#vector{x=A1, y=A2, z=A3}, #vector{x=B1, y=B2, z=B3}) ->
314 #vector{x = A2*B3 - A3*B2,
315 y = A3*B1 - A1*B3,
316 z = A1*B2 - A2*B1}.
318 vector_normalize(V) ->
319 Mag = vector_mag(V),
320 if Mag == 0 ->
321 #vector{x=0, y=0, z=0};
322 true ->
323 vector_scalar_mult(V, 1/vector_mag(V))
324 end.
326 vector_neg(#vector{x=X, y=Y, z=Z}) ->
327 #vector{x=-X, y=-Y, z=-Z}.
329 vector_reflect_about_normal(Vector, Normal) ->
330 vector_sub(
331 vector_scalar_mult(
332 Normal,
333 2*vector_dot_product(Normal, Vector)),
334 Vector).
336 vector_rotate(V1, _V2) ->
337 %TODO: implement using quaternions
340 object_diffuse_colour(#sphere{material=#material{colour=C}}) ->
342 object_diffuse_colour(_Unknown) ->
343 ?UNKNOWN_COLOUR.
344 object_specular_power(#sphere{material=#material{specular_power=SP}}) ->
346 object_shininess(#sphere{material=#material{shininess=S}}) ->
348 object_reflectivity(#sphere{material=#material{reflectivity=R}}) ->
351 point_on_sphere(#sphere{radius=Radius, center=#vector{x=XC, y=YC, z=ZC}},
352 #vector{x=X, y=Y, z=Z}) ->
353 Epsilon = 0.001,
354 Epsilon > abs(
355 ((X-XC)*(X-XC) + (Y-YC)*(Y-YC) + (Z-ZC)*(Z-ZC)) - Radius*Radius).
357 colour_to_vector(#colour{r=R, g=G, b=B}) ->
358 #vector{x=R, y=G, z=B}.
359 vector_to_colour(#vector{x=X, y=Y, z=Z}) ->
360 #colour{r=X, g=Y, b=Z}.
361 colour_to_pixel(#colour{r=R, g=G, b=B}) ->
362 {R, G, B}.
364 % returns a list of objects in the scene
365 % camera is assumed to be the first element in the scene
366 scene() ->
367 [#camera{location=#vector{x=0, y=0, z=0},
368 rotation=#vector{x=0, y=0, z=0},
369 fov=90,
370 screen=#screen{width=4, height=3}},
371 #point_light{diffuse_colour=#colour{r=1, g=1, b=0.5},
372 location=#vector{x=5, y=-2, z=0},
373 specular_colour=#colour{r=1, g=1, b=1}},
374 #point_light{diffuse_colour=#colour{r=1, g=0, b=0.5},
375 location=#vector{x=-10, y=0, z=7},
376 specular_colour=#colour{r=1, g=0, b=0.5}},
377 #sphere{radius=4,
378 center=#vector{x=4, y=0, z=10},
379 material=#material{
380 colour=#colour{r=0, g=0.5, b=1},
381 specular_power=20,
382 shininess=1,
383 reflectivity=0.1}},
384 #sphere{radius=4,
385 center=#vector{x=-5, y=3, z=9},
386 material=#material{
387 colour=#colour{r=1, g=0.5, b=0},
388 specular_power=4,
389 shininess=0.25,
390 reflectivity=0.5}},
391 #sphere{radius=4,
392 center=#vector{x=-5, y=-2, z=10},
393 material=#material{
394 colour=#colour{r=0.5, g=1, b=0},
395 specular_power=20,
396 shininess=0.25,
397 reflectivity=0.7}},
398 #triangle{v1=#vector{x=2, y=1.5, z=0},
399 v2=#vector{x=2, y=1.5, z=10},
400 v3=#vector{x=-2, y=1.5, z=0},
401 material=#material{
402 colour=#colour{r=0.5, g=0, b=1},
403 specular_power=40,
404 shininess=1,
405 reflectivity=1}}
409 % assumes Pixels are ordered in a row by row fasion
410 write_pixels_to_ppm(Width, Height, MaxValue, Pixels, Filename) ->
411 case file:open(Filename, write) of
412 {ok, IoDevice} ->
413 io:format("file opened~n", []),
414 io:format(IoDevice, "P3~n", []),
415 io:format(IoDevice, "~p ~p~n", [Width, Height]),
416 io:format(IoDevice, "~p~n", [MaxValue]),
417 lists:foreach(
418 fun({R, G, B}) ->
419 io:format(IoDevice, "~p ~p ~p ",
420 [lists:min([trunc(R*MaxValue), MaxValue]),
421 lists:min([trunc(G*MaxValue), MaxValue]),
422 lists:min([trunc(B*MaxValue), MaxValue])]) end,
423 Pixels),
424 file:close(IoDevice),
425 io:format("done~n", []);
426 error ->
427 io:format("error opening file~n", [])
428 end.
430 go() ->
431 go(16, 12, "/tmp/traced.ppm", 3).
432 go(Width, Height, Filename, Recursion_depth) ->
433 write_pixels_to_ppm(Width,
434 Height,
435 255,
436 raytraced_pixel_list(Width,
437 Height,
438 scene(),
439 Recursion_depth),
440 Filename).
442 % testing
443 scene_test() ->
444 io:format("testing the scene function", []),
445 case scene() of
446 [{camera,
447 {vector, 0, 0, 0},
448 {vector, 0, 0, 0},
450 {screen, 4, 3}},
451 {point_light,
452 {colour, 1, 1, 0.5},
453 {vector, 5, -2, 0},
454 {colour, 1, 1, 1}},
455 {point_light,
456 {colour, 1, 0, 0.5},
457 {vector, -10, 0, 7},
458 {colour, 1, 0, 0.5}},
459 {sphere,
461 {vector, 4, 0, 10},
462 {material, {colour, 0, 0.5, 1}, 20, 1, 0.1}},
463 {sphere,
465 {vector, -5, 3, 9},
466 {material, {colour, 1, 0.5, 0}, 4, 0.25, 0.5}},
467 {sphere,
469 {vector, -5, -2, 10},
470 {material, {colour, 0.5, 1, 0}, 20, 0.25, 0.7}},
471 {triangle,
472 {vector, 2, 1.5, 0},
473 {vector, 2, 1.5, 10},
474 {vector, -2, 1.5, 0},
475 {material, {colour, 0.5, 0, 1}, 40, 1, 1}}
476 ] ->
477 true;
478 _Else ->
479 false
480 end.
482 passing_test() ->
483 io:format("this test always passes", []),
484 true.
486 run_tests() ->
487 Tests = [fun scene_test/0,
488 fun passing_test/0,
489 fun vector_equality_test/0,
490 fun vector_addition_test/0,
491 fun vector_subtraction_test/0,
492 fun vector_square_mag_test/0,
493 fun vector_mag_test/0,
494 fun vector_scalar_multiplication_test/0,
495 fun vector_dot_product_test/0,
496 fun vector_cross_product_test/0,
497 fun vector_normalization_test/0,
498 fun vector_negation_test/0,
499 % fun ray_through_pixel_test/0,
500 fun ray_shooting_test/0,
501 fun point_on_screen_test/0,
502 fun nearest_object_intersecting_ray_test/0,
503 fun focal_length_test/0,
504 % fun vector_rotation_test/0,
505 fun object_normal_at_point_test/0,
506 fun vector_reflect_about_normal_test/0,
507 fun ray_sphere_intersection_test/0
509 run_tests(Tests, 1, true).
511 run_tests([], _Num, Success) ->
512 case Success of
513 true ->
514 io:format("Success!~n", []),
516 _Else ->
517 io:format("some tests failed~n", []),
518 failed
519 end;
521 run_tests([First_test|Rest_of_tests], Num, Success_so_far) ->
522 io:format("test #~p: ", [Num]),
523 Current_success = First_test(),
524 case Current_success of
525 true ->
526 io:format(" - OK~n", []);
527 _Else ->
528 io:format(" - FAILED~n", [])
529 end,
530 run_tests(Rest_of_tests, Num + 1, Current_success and Success_so_far).
532 vector_equality_test() ->
533 io:format("vector equality"),
534 Vector1 = #vector{x=0, y=0, z=0},
535 Vector2 = #vector{x=1234, y=-234, z=0},
536 Vector3 = #vector{x=0.0983, y=0.0214, z=0.12342},
537 Vector4 = #vector{x=0.0984, y=0.0213, z=0.12341},
538 Vector5 = #vector{x=10/3, y=-10/6, z=8/7},
539 Vector6 = #vector{x=3.3, y=-1.6, z=1.1},
541 Subtest1 = vectors_equal(Vector1, Vector1)
542 and vectors_equal(Vector2, Vector2)
543 and not (vectors_equal(Vector1, Vector2))
544 and not (vectors_equal(Vector2, Vector1)),
545 Subtest2 = vectors_equal(Vector3, Vector4, 0.0001),
546 Subtest3 = vectors_equal(Vector5, Vector6, 0.1),
548 Subtest1 and Subtest2 and Subtest3.
551 vector_addition_test() ->
552 io:format("vector addition", []),
553 Vector0 = vector_add(
554 #vector{x=3, y=7, z=-3},
555 #vector{x=0, y=-24, z=123}),
556 Subtest1 = (Vector0#vector.x == 3)
557 and (Vector0#vector.y == -17)
558 and (Vector0#vector.z == 120),
560 Vector1 = #vector{x=5, y=0, z=984},
561 Vector2 = vector_add(Vector1, Vector1),
562 Subtest2 = (Vector2#vector.x == Vector1#vector.x*2)
563 and (Vector2#vector.y == Vector1#vector.y*2)
564 and (Vector2#vector.z == Vector1#vector.z*2),
566 Vector3 = #vector{x=908, y=-098, z=234},
567 Vector4 = vector_add(Vector3, #vector{x=0, y=0, z=0}),
568 Subtest3 = vectors_equal(Vector3, Vector4),
570 Subtest1 and Subtest2 and Subtest3.
572 vector_subtraction_test() ->
573 io:format("vector subtraction", []),
574 Vector1 = #vector{x=0, y=0, z=0},
575 Vector2 = #vector{x=8390, y=-2098, z=939},
576 Vector3 = #vector{x=1, y=1, z=1},
577 Vector4 = #vector{x=-1, y=-1, z=-1},
579 Subtest1 = vectors_equal(Vector1, vector_sub(Vector1, Vector1)),
580 Subtest2 = vectors_equal(Vector3, vector_sub(Vector3, Vector1)),
581 Subtest3 = not vectors_equal(Vector3, vector_sub(Vector1, Vector3)),
582 Subtest4 = vectors_equal(Vector4, vector_sub(Vector4, Vector1)),
583 Subtest5 = not vectors_equal(Vector4, vector_sub(Vector1, Vector4)),
584 Subtest5 = vectors_equal(vector_add(Vector2, Vector4),
585 vector_sub(Vector2, Vector3)),
587 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5.
589 vector_square_mag_test() ->
590 io:format("vector square magnitude test", []),
591 Vector1 = #vector{x=0, y=0, z=0},
592 Vector2 = #vector{x=1, y=1, z=1},
593 Vector3 = #vector{x=3, y=-4, z=0},
595 Subtest1 = (0 == vector_square_mag(Vector1)),
596 Subtest2 = (3 == vector_square_mag(Vector2)),
597 Subtest3 = (25 == vector_square_mag(Vector3)),
599 Subtest1 and Subtest2 and Subtest3.
601 vector_mag_test() ->
602 io:format("vector magnitude test", []),
603 Vector1 = #vector{x=0, y=0, z=0},
604 Vector2 = #vector{x=1, y=1, z=1},
605 Vector3 = #vector{x=3, y=-4, z=0},
607 Subtest1 = (0 == vector_mag(Vector1)),
608 Subtest2 = (math:sqrt(3) == vector_mag(Vector2)),
609 Subtest3 = (5 == vector_mag(Vector3)),
611 Subtest1 and Subtest2 and Subtest3.
613 vector_scalar_multiplication_test() ->
614 io:format("scalar multiplication test", []),
615 Vector1 = #vector{x=0, y=0, z=0},
616 Vector2 = #vector{x=1, y=1, z=1},
617 Vector3 = #vector{x=3, y=-4, z=0},
619 Subtest1 = vectors_equal(Vector1, vector_scalar_mult(Vector1, 45)),
620 Subtest2 = vectors_equal(Vector1, vector_scalar_mult(Vector1, -13)),
621 Subtest3 = vectors_equal(Vector1, vector_scalar_mult(Vector3, 0)),
622 Subtest4 = vectors_equal(#vector{x=4, y=4, z=4},
623 vector_scalar_mult(Vector2, 4)),
624 Subtest5 = vectors_equal(Vector3, vector_scalar_mult(Vector3, 1)),
625 Subtest6 = not vectors_equal(Vector3, vector_scalar_mult(Vector3, -3)),
627 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
629 vector_dot_product_test() ->
630 io:format("dot product test", []),
631 Vector1 = #vector{x=1, y=3, z=-5},
632 Vector2 = #vector{x=4, y=-2, z=-1},
633 Vector3 = #vector{x=0, y=0, z=0},
634 Vector4 = #vector{x=1, y=0, z=0},
635 Vector5 = #vector{x=0, y=1, z=0},
637 Subtest1 = 3 == vector_dot_product(Vector1, Vector2),
638 Subtest2 = vector_dot_product(Vector2, Vector2)
639 == vector_square_mag(Vector2),
640 Subtest3 = 0 == vector_dot_product(Vector3, Vector1),
641 Subtest4 = 0 == vector_dot_product(Vector4, Vector5),
643 Subtest1 and Subtest2 and Subtest3 and Subtest4.
645 vector_cross_product_test() ->
646 io:format("cross product test", []),
647 Vector1 = #vector{x=0, y=0, z=0},
648 Vector2 = #vector{x=1, y=0, z=0},
649 Vector3 = #vector{x=0, y=1, z=0},
650 Vector4 = #vector{x=0, y=0, z=1},
651 Vector5 = #vector{x=1, y=2, z=3},
652 Vector6 = #vector{x=4, y=5, z=6},
653 Vector7 = #vector{x=-3, y=6, z=-3},
654 Vector8 = #vector{x=-1, y=0, z=0},
655 Vector9 = #vector{x=-9, y=8, z=433},
657 Subtest1 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector2)),
658 Subtest2 = vectors_equal(Vector1, vector_cross_product(Vector2, Vector8)),
659 Subtest3 = vectors_equal(Vector2, vector_cross_product(Vector3, Vector4)),
660 Subtest4 = vectors_equal(Vector7, vector_cross_product(Vector5, Vector6)),
661 Subtest5 = vectors_equal(
662 vector_cross_product(Vector7,
663 vector_add(Vector8, Vector9)),
664 vector_add(
665 vector_cross_product(Vector7, Vector8),
666 vector_cross_product(Vector7, Vector9))),
667 Subtest6 = vectors_equal(Vector1,
668 vector_add(
669 vector_add(
670 vector_cross_product(
671 Vector7,
672 vector_cross_product(Vector8, Vector9)),
673 vector_cross_product(
674 Vector8,
675 vector_cross_product(Vector9, Vector7))),
676 vector_cross_product(
677 Vector9,
678 vector_cross_product(Vector7, Vector8)))),
680 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
682 vector_normalization_test() ->
683 io:format("normalization test", []),
684 Vector1 = #vector{x=0, y=0, z=0},
685 Vector2 = #vector{x=1, y=0, z=0},
686 Vector3 = #vector{x=5, y=0, z=0},
688 Subtest1 = vectors_equal(Vector1, vector_normalize(Vector1)),
689 Subtest2 = vectors_equal(Vector2, vector_normalize(Vector2)),
690 Subtest3 = vectors_equal(Vector2, vector_normalize(Vector3)),
691 Subtest4 = vectors_equal(Vector2, vector_normalize(
692 vector_scalar_mult(Vector2, 324))),
694 Subtest1 and Subtest2 and Subtest3 and Subtest4.
696 vector_negation_test() ->
697 io:format("vector negation test", []),
698 Vector1 = #vector{x=0, y=0, z=0},
699 Vector2 = #vector{x=4, y=-5, z=6},
701 Subtest1 = vectors_equal(Vector1, vector_neg(Vector1)),
702 Subtest2 = vectors_equal(Vector2, vector_neg(vector_neg(Vector2))),
704 Subtest1 and Subtest2.
706 ray_through_pixel_test() ->
707 io:format("ray through pixel test", []),
708 false.
710 ray_shooting_test() ->
711 io:format("ray shooting test"),
712 Vector1 = #vector{x=0, y=0, z=0},
713 Vector2 = #vector{x=1, y=0, z=0},
715 Subtest1 = vectors_equal(
716 (shoot_ray(Vector1, Vector2))#ray.direction,
717 Vector2),
719 Subtest1.
721 ray_sphere_intersection_test() ->
722 io:format("ray sphere intersection test", []),
724 Sphere = #sphere{
725 radius=3,
726 center=#vector{x = 0, y=0, z=10},
727 material=#material{
728 colour=#colour{r=0.4, g=0.4, b=0.4}}},
729 Ray1 = #ray{
730 origin=#vector{x=0, y=0, z=0},
731 direction=#vector{x=0, y=0, z=1}},
732 Ray2 = #ray{
733 origin=#vector{x=3, y=0, z=0},
734 direction=#vector{x=0, y=0, z=1}},
735 Ray3 = #ray{
736 origin=#vector{x=4, y=0, z=0},
737 direction=#vector{x=0, y=0, z=1}},
738 Subtest1 = ray_sphere_intersect(Ray1, Sphere) == 7.0,
739 Subtest2 = ray_sphere_intersect(Ray2, Sphere) == 10.0,
740 Subtest3 = ray_sphere_intersect(Ray3, Sphere) == infinity,
741 Subtest1 and Subtest2 and Subtest3.
743 point_on_screen_test() ->
744 io:format("point on screen test", []),
745 Camera1 = #camera{location=#vector{x=0, y=0, z=0},
746 rotation=#vector{x=0, y=0, z=0},
747 fov=90,
748 screen=#screen{width=1, height=1}},
749 Camera2 = #camera{location=#vector{x=0, y=0, z=0},
750 rotation=#vector{x=0, y=0, z=0},
751 fov=90,
752 screen=#screen{width=640, height=480}},
754 Subtest1 = vectors_equal(
755 #vector{x=0, y=0, z=0.5},
756 point_on_screen(0.5, 0.5, Camera1)),
757 Subtest2 = vectors_equal(
758 #vector{x=-0.5, y=-0.5, z=0.5},
759 point_on_screen(0, 0, Camera1)),
760 Subtest3 = vectors_equal(
761 #vector{x=0.5, y=0.5, z=0.5},
762 point_on_screen(1, 1, Camera1)),
763 Subtest4 = vectors_equal(
764 point_on_screen(0, 0, Camera2),
765 #vector{x=-320, y=-240, z=320}),
766 Subtest5 = vectors_equal(
767 point_on_screen(1, 1, Camera2),
768 #vector{x=320, y=240, z=320}),
769 Subtest6 = vectors_equal(
770 point_on_screen(0.5, 0.5, Camera2),
771 #vector{x=0, y=0, z=320}),
773 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6.
775 nearest_object_intersecting_ray_test() ->
776 io:format("nearest object intersecting ray test", []),
777 % test to make sure that we really get the closest object
778 Sphere1=#sphere{radius=5,
779 center=#vector{x=0, y=0, z=10},
780 material=#material{
781 colour=#colour{r=0, g=0, b=0.03}}},
782 Sphere2=#sphere{radius=5,
783 center=#vector{x=0, y=0, z=20},
784 material=#material{
785 colour=#colour{r=0, g=0, b=0.06}}},
786 Sphere3=#sphere{radius=5,
787 center=#vector{x=0, y=0, z=30},
788 material=#material{
789 colour=#colour{r=0, g=0, b=0.09}}},
790 Sphere4=#sphere{radius=5,
791 center=#vector{x=0, y=0, z=-10},
792 material=#material{
793 colour=#colour{r=0, g=0, b=-0.4}}},
794 Scene1=[Sphere1, Sphere2, Sphere3, Sphere4],
795 Ray1=#ray{origin=#vector{x=0, y=0, z=0},
796 direction=#vector{x=0, y=0, z=1}},
798 {Object1, Distance1, Hit_location, Normal} = nearest_object_intersecting_ray(
799 Ray1, Scene1),
800 Subtest1 = (Object1 == Sphere1) and (Distance1 == 5)
801 and vectors_equal(Normal, vector_neg(Ray1#ray.direction))
802 and point_on_sphere(Sphere1, Hit_location),
804 Subtest1.
806 focal_length_test() ->
807 Epsilon = 0.1,
808 Size = 36,
809 io:format("focal length test", []),
810 lists:foldl(
811 fun({Focal_length, Dimension}, Matches) ->
812 %Result = focal_length(Dimension, Size),
813 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
814 Matches
815 and ((Focal_length + Epsilon >= focal_length(
816 Dimension, Size))
817 and (Focal_length - Epsilon =< focal_length(
818 Dimension, Size)))
819 end, true,
820 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
822 vector_rotation_test() ->
823 io:format("vector rotation test", []),
824 Vector1 = #vector{x=0, y=0, z=0},
825 Vector2 = #vector{x=0, y=1, z=0},
826 Vector3 = #vector{x=90, y=0, z=0},
827 Vector4 = #vector{x=45, y=0, z=0},
828 Vector5 = #vector{x=30.11, y=-988.2, z=92.231},
829 Vector6 = #vector{x=0, y=0, z=1},
831 Subtest1 = vectors_equal(
832 vector_rotate(Vector1, Vector1),
833 Vector1),
834 Subtest2 = vectors_equal(
835 vector_rotate(Vector5, Vector1),
836 Vector5),
837 Subtest3 = vectors_equal(
838 vector_rotate(
839 vector_rotate(Vector5, Vector4),
840 Vector4),
841 vector_rotate(Vector5, Vector3)),
842 Subtest4 = vectors_equal(
843 Vector6,
844 vector_rotate(Vector2, Vector3)),
846 Subtest1 and Subtest2 and Subtest3 and Subtest4.
848 object_normal_at_point_test() ->
849 io:format("object normal at point test"),
850 Sphere1 = #sphere{radius=13.5,
851 center=#vector{x=0, y=0, z=0},
852 material=#material{
853 colour=#colour{r=0, g=0, b=0}}},
854 Point1 = #vector{x=13.5, y=0, z=0},
855 Point2 = #vector{x=0, y=13.5, z=0},
856 Point3 = #vector{x=0, y=0, z=13.5},
857 Point4 = vector_neg(Point1),
858 Point5 = vector_neg(Point2),
859 Point6 = vector_neg(Point3),
861 % sphere object tests
862 Subtest1 = vectors_equal(
863 vector_normalize(Point1),
864 object_normal_at_point(Sphere1, Point1)),
865 Subtest2 = vectors_equal(
866 vector_normalize(Point2),
867 object_normal_at_point(Sphere1, Point2)),
868 Subtest3 = vectors_equal(
869 vector_normalize(Point3),
870 object_normal_at_point(Sphere1, Point3)),
871 Subtest4 = vectors_equal(
872 vector_normalize(Point4),
873 object_normal_at_point(Sphere1, Point4)),
874 Subtest5 = vectors_equal(
875 vector_normalize(Point5),
876 object_normal_at_point(Sphere1, Point5)),
877 Subtest6 = vectors_equal(
878 vector_normalize(Point6),
879 object_normal_at_point(Sphere1, Point6)),
880 Subtest7 = not vectors_equal(
881 vector_normalize(Point1),
882 object_normal_at_point(Sphere1, Point4)),
884 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
885 and Subtest7.
887 vector_reflect_about_normal_test() ->
888 io:format("vector reflect about normal", []),
889 Vector1 = #vector{x=-1, y=-1, z=0},
890 Vector2 = #vector{x=0, y=-1, z=0},
891 Vector3 = #vector{x=1, y=-1, z=0},
892 Vector4 = #vector{x=-1, y=0, z=0},
894 Subtest1 = vectors_equal(vector_reflect_about_normal(
895 Vector1,
896 vector_normalize(Vector2)),
897 Vector3),
899 Subtest2 = vectors_equal(
900 vector_reflect_about_normal(
901 Vector2,
902 vector_normalize(Vector1)),
903 Vector4),
905 Subtest1 and Subtest2.