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/>.
22 %% * two object types:
24 %% * triangles (not done)
26 %% * shadows (not done)
27 %% * lighting based on local illumination models
28 %% * ambient (not done)
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)
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, _
, _
) ->
61 raytraced_pixel_list(Width
, Height
, Scene
, Recursion_depth
)
62 when Width
> 0, Height
> 0 ->
67 % coordinates passed as a percentage
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
,
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
,
98 lighting_function(Ray
, Object
, Hit_location
, Hit_normal
, Scene
,
101 fun (#point_light
{diffuse_colour
=Light_colour
,
102 location
=Light_location
,
103 specular_colour
=Specular_colour
},
108 pixel_colour_from_ray(
109 #ray
{origin
=Hit_location
,
110 direction
=vector_reflect_about_normal(
111 vector_neg(Ray#ray
.direction
), Hit_normal
)},
114 object_reflectivity(Object
)),
116 vector_component_mult(
117 colour_to_vector(Light_colour
),
123 specular_term(Ray#ray
.direction
,
127 object_specular_power(Object
),
128 object_shininess(Object
),
131 (_Not_a_point_light
, Final_colour
) ->
134 #vector
{x
=0, y
=0, z
=0},
137 diffuse_term(Object
, Light_location
, Hit_location
, Hit_normal
) ->
139 colour_to_vector(object_diffuse_colour(Object
)),
141 vector_dot_product(Hit_normal
,
143 vector_sub(Light_location
,
146 specular_term(EyeVector
, Light_location
, Hit_location
, Hit_normal
,
147 Specular_power
, Shininess
, Specular_colour
) ->
149 colour_to_vector(Specular_colour
),
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
, []) ->
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
,
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", []),
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(
194 %io:format("no closer obj found~n", []),
195 nearest_object_intersecting_ray(Ray
,
203 ray_object_intersect(Ray
, Object
) ->
206 ray_sphere_intersect(Ray
, Object
);
208 ray_triangle_intersect(Ray
, Object
);
213 object_normal_at_point(#sphere
{center
=Center
}, Point
) ->
215 vector_sub(Point
, Center
)).
217 ray_sphere_intersect(
222 #sphere
{radius
=Radius
, center
=#vector
{
223 x
=Xc
, y
=Yc
, z
=Zc
}}) ->
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
>= Epsilon
) and (T1
>= Epsilon
) ->
235 %io:format("T0=~w T1=~w~n", [T0, T1]),
244 ray_triangle_intersect(Ray
, Triangle
) ->
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
,
257 #vector
{x
=0, y
=0, z
=1},
261 #vector
{x
= (X
-0.5) * Screen_width
,
265 y
= (Y
-0.5) * Screen_height
,
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
}) ->
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
,
318 vector_normalize(V
) ->
321 #vector
{x
=0, y
=0, z
=0};
323 vector_scalar_mult(V
, 1/vector_mag(V
))
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
) ->
333 2*vector_dot_product(Normal
, 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
) ->
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
}) ->
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
}) ->
363 colour_trunc(#colour
{r
=R
, g
=G
, b
=B
}) ->
364 #colour
{r
=trunc(R
), g
=trunc(G
), b
=trunc(B
)}.
366 % returns a list of objects in the scene
367 % camera is assumed to be the first element in the scene
369 [#camera
{location
=#vector
{x
=0, y
=0, z
=0},
370 rotation
=#vector
{x
=0, y
=0, z
=0},
372 screen
=#screen
{width
=4, height
=3}},
373 #point_light
{diffuse_colour
=#colour
{r
=1, g
=1, b
=0.5},
374 location
=#vector
{x
=5, y
=-2, z
=0},
375 specular_colour
=#colour
{r
=1, g
=1, b
=1}},
376 #point_light
{diffuse_colour
=#colour
{r
=1, g
=0, b
=0.5},
377 location
=#vector
{x
=-10, y
=0, z
=7},
378 specular_colour
=#colour
{r
=1, g
=0, b
=0.5}},
380 center
=#vector
{x
=4, y
=0, z
=10},
382 colour
=#colour
{r
=0, g
=0.5, b
=1},
387 center
=#vector
{x
=-5, y
=3, z
=9},
389 colour
=#colour
{r
=1, g
=0.5, b
=0},
394 center
=#vector
{x
=-5, y
=-2, z
=10},
396 colour
=#colour
{r
=0.5, g
=1, b
=0},
400 #triangle
{v1
=#vector
{x
=2, y
=1.5, z
=0},
401 v2
=#vector
{x
=2, y
=1.5, z
=10},
402 v3
=#vector
{x
=-2, y
=1.5, z
=0},
404 colour
=#colour
{r
=0.5, g
=0, b
=1},
411 % assumes Pixels are ordered in a row by row fasion
412 write_pixels_to_ppm(Width
, Height
, MaxValue
, Pixels
, Filename
) ->
413 case file:open(Filename
, write
) of
415 io:format("file opened~n", []),
416 io:format(IoDevice
, "P3~n", []),
417 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
418 io:format(IoDevice
, "~p~n", [MaxValue
]),
421 io:format(IoDevice
, "~p ~p ~p ",
422 [lists:min([trunc(R
*MaxValue
), MaxValue
]),
423 lists:min([trunc(G
*MaxValue
), MaxValue
]),
424 lists:min([trunc(B
*MaxValue
), MaxValue
])]) end,
426 file:close(IoDevice
),
427 io:format("done~n", []);
429 io:format("error opening file~n", [])
433 go(16, 12, "/tmp/traced.ppm").
434 go(Width
, Height
, Filename
) ->
435 write_pixels_to_ppm(Width
,
438 raytraced_pixel_list(Width
,
446 io:format("testing the scene function", []),
460 {colour
, 1, 0, 0.5}},
464 {material
, {colour
, 0, 0.5, 1}, 20, 1, 0.1}},
468 {material
, {colour
, 1, 0.5, 0}, 4, 0.25, 0.5}},
471 {vector
, -5, -2, 10},
472 {material
, {colour
, 0.5, 1, 0}, 20, 0.25, 0.7}},
475 {vector
, 2, 1.5, 10},
476 {vector
, -2, 1.5, 0},
477 {material
, {colour
, 0.5, 0, 1}, 40, 1, 1}}
485 io:format("this test always fails", []),
489 io:format("this test always passes", []),
493 Tests
= [fun scene_test
/0,
495 fun vector_equality_test
/0,
496 fun vector_addition_test
/0,
497 fun vector_subtraction_test
/0,
498 fun vector_square_mag_test
/0,
499 fun vector_mag_test
/0,
500 fun vector_scalar_multiplication_test
/0,
501 fun vector_dot_product_test
/0,
502 fun vector_cross_product_test
/0,
503 fun vector_normalization_test
/0,
504 fun vector_negation_test
/0,
505 % fun ray_through_pixel_test/0,
506 fun ray_shooting_test
/0,
507 fun point_on_screen_test
/0,
508 fun nearest_object_intersecting_ray_test
/0,
509 fun focal_length_test
/0,
510 % fun vector_rotation_test/0,
511 fun object_normal_at_point_test
/0,
512 fun vector_reflect_about_normal_test
/0
514 run_tests(Tests
, 1, true
).
516 run_tests([], _Num
, Success
) ->
519 io:format("Success!~n", []),
522 io:format("some tests failed~n", []),
526 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
527 io:format("test #~p: ", [Num
]),
528 Current_success
= First_test(),
529 case Current_success
of
531 io:format(" - OK~n", []);
533 io:format(" - FAILED~n", [])
535 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
537 vector_equality_test() ->
538 io:format("vector equality"),
539 Vector1
= #vector
{x
=0, y
=0, z
=0},
540 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
541 Vector3
= #vector
{x
=0.0983, y
=0.0214, z
=0.12342},
542 Vector4
= #vector
{x
=0.0984, y
=0.0213, z
=0.12341},
543 Vector5
= #vector
{x
=10/3, y
=-10/6, z
=8/7},
544 Vector6
= #vector
{x
=3.3, y
=-1.6, z
=1.1},
546 Subtest1
= vectors_equal(Vector1
, Vector1
)
547 and
vectors_equal(Vector2
, Vector2
)
548 and
not (vectors_equal(Vector1
, Vector2
))
549 and
not (vectors_equal(Vector2
, Vector1
)),
550 Subtest2
= vectors_equal(Vector3
, Vector4
, 0.0001),
551 Subtest3
= vectors_equal(Vector5
, Vector6
, 0.1),
553 Subtest1 and Subtest2 and Subtest3
.
556 vector_addition_test() ->
557 io:format("vector addition", []),
558 Vector0
= vector_add(
559 #vector
{x
=3, y
=7, z
=-3},
560 #vector
{x
=0, y
=-24, z
=123}),
561 Subtest1
= (Vector0#vector
.x
== 3)
562 and (Vector0#vector
.y
== -17)
563 and (Vector0#vector
.z
== 120),
565 Vector1
= #vector
{x
=5, y
=0, z
=984},
566 Vector2
= vector_add(Vector1
, Vector1
),
567 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
568 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
569 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
571 Vector3
= #vector
{x
=908, y
=-098, z
=234},
572 Vector4
= vector_add(Vector3
, #vector
{x
=0, y
=0, z
=0}),
573 Subtest3
= vectors_equal(Vector3
, Vector4
),
575 Subtest1 and Subtest2 and Subtest3
.
577 vector_subtraction_test() ->
578 io:format("vector subtraction", []),
579 Vector1
= #vector
{x
=0, y
=0, z
=0},
580 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
581 Vector3
= #vector
{x
=1, y
=1, z
=1},
582 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
584 Subtest1
= vectors_equal(Vector1
, vector_sub(Vector1
, Vector1
)),
585 Subtest2
= vectors_equal(Vector3
, vector_sub(Vector3
, Vector1
)),
586 Subtest3
= not
vectors_equal(Vector3
, vector_sub(Vector1
, Vector3
)),
587 Subtest4
= vectors_equal(Vector4
, vector_sub(Vector4
, Vector1
)),
588 Subtest5
= not
vectors_equal(Vector4
, vector_sub(Vector1
, Vector4
)),
589 Subtest5
= vectors_equal(vector_add(Vector2
, Vector4
),
590 vector_sub(Vector2
, Vector3
)),
592 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
594 vector_square_mag_test() ->
595 io:format("vector square magnitude test", []),
596 Vector1
= #vector
{x
=0, y
=0, z
=0},
597 Vector2
= #vector
{x
=1, y
=1, z
=1},
598 Vector3
= #vector
{x
=3, y
=-4, z
=0},
600 Subtest1
= (0 == vector_square_mag(Vector1
)),
601 Subtest2
= (3 == vector_square_mag(Vector2
)),
602 Subtest3
= (25 == vector_square_mag(Vector3
)),
604 Subtest1 and Subtest2 and Subtest3
.
607 io:format("vector magnitude test", []),
608 Vector1
= #vector
{x
=0, y
=0, z
=0},
609 Vector2
= #vector
{x
=1, y
=1, z
=1},
610 Vector3
= #vector
{x
=3, y
=-4, z
=0},
612 Subtest1
= (0 == vector_mag(Vector1
)),
613 Subtest2
= (math:sqrt(3) == vector_mag(Vector2
)),
614 Subtest3
= (5 == vector_mag(Vector3
)),
616 Subtest1 and Subtest2 and Subtest3
.
618 vector_scalar_multiplication_test() ->
619 io:format("scalar multiplication test", []),
620 Vector1
= #vector
{x
=0, y
=0, z
=0},
621 Vector2
= #vector
{x
=1, y
=1, z
=1},
622 Vector3
= #vector
{x
=3, y
=-4, z
=0},
624 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
625 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
626 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
627 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
628 vector_scalar_mult(Vector2
, 4)),
629 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
630 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
632 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
634 vector_dot_product_test() ->
635 io:format("dot product test", []),
636 Vector1
= #vector
{x
=1, y
=3, z
=-5},
637 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
638 Vector3
= #vector
{x
=0, y
=0, z
=0},
639 Vector4
= #vector
{x
=1, y
=0, z
=0},
640 Vector5
= #vector
{x
=0, y
=1, z
=0},
642 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
643 Subtest2
= vector_dot_product(Vector2
, Vector2
)
644 == vector_square_mag(Vector2
),
645 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
646 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
648 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
650 vector_cross_product_test() ->
651 io:format("cross product test", []),
652 Vector1
= #vector
{x
=0, y
=0, z
=0},
653 Vector2
= #vector
{x
=1, y
=0, z
=0},
654 Vector3
= #vector
{x
=0, y
=1, z
=0},
655 Vector4
= #vector
{x
=0, y
=0, z
=1},
656 Vector5
= #vector
{x
=1, y
=2, z
=3},
657 Vector6
= #vector
{x
=4, y
=5, z
=6},
658 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
659 Vector8
= #vector
{x
=-1, y
=0, z
=0},
660 Vector9
= #vector
{x
=-9, y
=8, z
=433},
662 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
663 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
664 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
665 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
666 Subtest5
= vectors_equal(
667 vector_cross_product(Vector7
,
668 vector_add(Vector8
, Vector9
)),
670 vector_cross_product(Vector7
, Vector8
),
671 vector_cross_product(Vector7
, Vector9
))),
672 Subtest6
= vectors_equal(Vector1
,
675 vector_cross_product(
677 vector_cross_product(Vector8
, Vector9
)),
678 vector_cross_product(
680 vector_cross_product(Vector9
, Vector7
))),
681 vector_cross_product(
683 vector_cross_product(Vector7
, Vector8
)))),
685 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
687 vector_normalization_test() ->
688 io:format("normalization test", []),
689 Vector1
= #vector
{x
=0, y
=0, z
=0},
690 Vector2
= #vector
{x
=1, y
=0, z
=0},
691 Vector3
= #vector
{x
=5, y
=0, z
=0},
693 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
694 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
695 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
696 Subtest4
= vectors_equal(Vector2
, vector_normalize(
697 vector_scalar_mult(Vector2
, 324))),
699 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
701 vector_negation_test() ->
702 io:format("vector negation test", []),
703 Vector1
= #vector
{x
=0, y
=0, z
=0},
704 Vector2
= #vector
{x
=4, y
=-5, z
=6},
706 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
707 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
709 Subtest1 and Subtest2
.
711 ray_through_pixel_test() ->
712 io:format("ray through pixel test", []),
715 ray_shooting_test() ->
716 io:format("ray shooting test"),
717 Vector1
= #vector
{x
=0, y
=0, z
=0},
718 Vector2
= #vector
{x
=1, y
=0, z
=0},
720 Subtest1
= vectors_equal(
721 (shoot_ray(Vector1
, Vector2
))#ray
.direction
,
726 ray_sphere_intersection_test() ->
729 center
=#vector
{x
= 0, y
=0, z
=10},
731 colour
=#colour
{r
=0.4, g
=0.4, b
=0.4}}},
733 origin
=#vector
{x
=0, y
=0, z
=0},
734 direction
=#vector
{x
=0, y
=0, z
=1}},
736 origin
=#vector
{x
=3, y
=0, z
=0},
737 direction
=#vector
{x
=0, y
=0, z
=1}},
739 origin
=#vector
{x
=4, y
=0, z
=0},
740 direction
=#vector
{x
=0, y
=0, z
=1}},
741 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray1
, Sphere
)]),
742 Subtest1
= ray_sphere_intersect(Ray1
, Sphere
) == 7.0,
743 Subtest2
= ray_sphere_intersect(Ray2
, Sphere
) == 10.0,
744 Subtest3
= ray_sphere_intersect(Ray3
, Sphere
) == none
,
745 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray2
, Sphere
)]),
746 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray3
, Sphere
)]),
747 Subtest1 and Subtest2 and Subtest3
.
749 point_on_screen_test() ->
750 io:format("point on screen test", []),
751 Camera1
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
752 rotation
=#vector
{x
=0, y
=0, z
=0},
754 screen
=#screen
{width
=1, height
=1}},
755 Camera2
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
756 rotation
=#vector
{x
=0, y
=0, z
=0},
758 screen
=#screen
{width
=640, height
=480}},
760 Subtest1
= vectors_equal(
761 #vector
{x
=0, y
=0, z
=0.5},
762 point_on_screen(0.5, 0.5, Camera1
)),
763 Subtest2
= vectors_equal(
764 #vector
{x
=-0.5, y
=-0.5, z
=0.5},
765 point_on_screen(0, 0, Camera1
)),
766 Subtest3
= vectors_equal(
767 #vector
{x
=0.5, y
=0.5, z
=0.5},
768 point_on_screen(1, 1, Camera1
)),
769 Subtest4
= vectors_equal(
770 point_on_screen(0, 0, Camera2
),
771 #vector
{x
=-320, y
=-240, z
=320}),
772 Subtest5
= vectors_equal(
773 point_on_screen(1, 1, Camera2
),
774 #vector
{x
=320, y
=240, z
=320}),
775 Subtest6
= vectors_equal(
776 point_on_screen(0.5, 0.5, Camera2
),
777 #vector
{x
=0, y
=0, z
=320}),
779 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
781 nearest_object_intersecting_ray_test() ->
782 io:format("nearest object intersecting ray test", []),
783 % test to make sure that we really get the closest object
784 Sphere1
=#sphere
{radius
=5,
785 center
=#vector
{x
=0, y
=0, z
=10},
787 colour
=#colour
{r
=0, g
=0, b
=0.03}}},
788 Sphere2
=#sphere
{radius
=5,
789 center
=#vector
{x
=0, y
=0, z
=20},
791 colour
=#colour
{r
=0, g
=0, b
=0.06}}},
792 Sphere3
=#sphere
{radius
=5,
793 center
=#vector
{x
=0, y
=0, z
=30},
795 colour
=#colour
{r
=0, g
=0, b
=0.09}}},
796 Sphere4
=#sphere
{radius
=5,
797 center
=#vector
{x
=0, y
=0, z
=-10},
799 colour
=#colour
{r
=0, g
=0, b
=-0.4}}},
800 Scene1
=[Sphere1
, Sphere2
, Sphere3
, Sphere4
],
801 Ray1
=#ray
{origin
=#vector
{x
=0, y
=0, z
=0},
802 direction
=#vector
{x
=0, y
=0, z
=1}},
804 {Object1
, Distance1
, Hit_location
, Normal
} = nearest_object_intersecting_ray(
806 Subtest1
= (Object1
== Sphere1
) and (Distance1
== 5)
807 and
vectors_equal(Normal
, vector_neg(Ray1#ray
.direction
))
808 and
point_on_sphere(Sphere1
, Hit_location
),
812 focal_length_test() ->
815 io:format("focal length test", []),
817 fun({Focal_length
, Dimension
}, Matches
) ->
818 %Result = focal_length(Dimension, Size),
819 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
821 and ((Focal_length
+ Epsilon
>= focal_length(
823 and (Focal_length
- Epsilon
=< focal_length(
826 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
828 vector_rotation_test() ->
829 io:format("vector rotation test", []),
830 Vector1
= #vector
{x
=0, y
=0, z
=0},
831 Vector2
= #vector
{x
=0, y
=1, z
=0},
832 Vector3
= #vector
{x
=90, y
=0, z
=0},
833 Vector4
= #vector
{x
=45, y
=0, z
=0},
834 Vector5
= #vector
{x
=30.11, y
=-988.2, z
=92.231},
835 Vector6
= #vector
{x
=0, y
=0, z
=1},
837 Subtest1
= vectors_equal(
838 vector_rotate(Vector1
, Vector1
),
840 Subtest2
= vectors_equal(
841 vector_rotate(Vector5
, Vector1
),
843 Subtest3
= vectors_equal(
845 vector_rotate(Vector5
, Vector4
),
847 vector_rotate(Vector5
, Vector3
)),
848 Subtest4
= vectors_equal(
850 vector_rotate(Vector2
, Vector3
)),
852 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
854 object_normal_at_point_test() ->
855 io:format("object normal at point test"),
856 Sphere1
= #sphere
{radius
=13.5,
857 center
=#vector
{x
=0, y
=0, z
=0},
859 colour
=#colour
{r
=0, g
=0, b
=0}}},
860 Point1
= #vector
{x
=13.5, y
=0, z
=0},
861 Point2
= #vector
{x
=0, y
=13.5, z
=0},
862 Point3
= #vector
{x
=0, y
=0, z
=13.5},
863 Point4
= vector_neg(Point1
),
864 Point5
= vector_neg(Point2
),
865 Point6
= vector_neg(Point3
),
867 % sphere object tests
868 Subtest1
= vectors_equal(
869 vector_normalize(Point1
),
870 object_normal_at_point(Sphere1
, Point1
)),
871 Subtest2
= vectors_equal(
872 vector_normalize(Point2
),
873 object_normal_at_point(Sphere1
, Point2
)),
874 Subtest3
= vectors_equal(
875 vector_normalize(Point3
),
876 object_normal_at_point(Sphere1
, Point3
)),
877 Subtest4
= vectors_equal(
878 vector_normalize(Point4
),
879 object_normal_at_point(Sphere1
, Point4
)),
880 Subtest5
= vectors_equal(
881 vector_normalize(Point5
),
882 object_normal_at_point(Sphere1
, Point5
)),
883 Subtest6
= vectors_equal(
884 vector_normalize(Point6
),
885 object_normal_at_point(Sphere1
, Point6
)),
886 Subtest7
= not
vectors_equal(
887 vector_normalize(Point1
),
888 object_normal_at_point(Sphere1
, Point4
)),
890 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
893 vector_reflect_about_normal_test() ->
894 io:format("vector reflect about normal", []),
895 Vector1
= #vector
{x
=-1, y
=-1, z
=0},
896 Vector2
= #vector
{x
=0, y
=-1, z
=0},
897 Vector3
= #vector
{x
=1, y
=-1, z
=0},
898 Vector4
= #vector
{x
=-1, y
=0, z
=0},
900 Subtest1
= vectors_equal(vector_reflect_about_normal(
902 vector_normalize(Vector2
)),
905 Subtest2
= vectors_equal(
906 vector_reflect_about_normal(
908 vector_normalize(Vector1
)),
911 Subtest1 and Subtest2
.