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, _
) ->
20 raytraced_pixel_list(Width
, Height
, Scene
) when Width
> 0, Height
> 0 ->
25 % coordinates passed as a percentage
29 {X
/Width
, Y
/Height
}, Scene
))) end,
30 lists:seq(0, Width
- 1)) end,
31 lists:seq(0, Height
- 1)).
33 % assumes X and Y are percentages of the screen dimensions
34 trace_ray_from_pixel({X
, Y
}, [Camera
|Rest_of_scene
]) ->
35 Ray
= ray_through_pixel(X
, Y
, Camera
),
36 case nearest_object_intersecting_ray(Ray
, Rest_of_scene
) of
37 {Nearest_object
, _Distance
, Hit_location
, Hit_normal
} ->
38 %io:format("hit: ~w~n", [{Nearest_object, _Distance}]),
40 vector_to_colour(lighting_function(Nearest_object
,
50 lighting_function(Object
, Hit_location
, Hit_normal
, Scene
) ->
52 fun (#point_light
{colour
=Light_colour
,
53 intensity
=Light_intensity
,
54 location
=Light_location
},
60 point_light_intensity(
61 #point_light
{colour
=Light_colour
,
62 intensity
=Light_intensity
,
63 location
=Light_location
},
67 colour_to_vector(object_colour(Object
))),
70 (_Not_a_point_light
, Total_intensity
) ->
73 #vector
{x
=0, y
=0, z
=0},
76 point_light_intensity(
77 #point_light
{colour
=Light_colour
,
78 location
=Light_location
},
83 colour_to_vector(Light_colour
),
88 vector_sub(Light_location
,
91 nearest_object_intersecting_ray(Ray
, Scene
) ->
92 nearest_object_intersecting_ray(
93 Ray
, none
, hitlocation
, hitnormal
, infinity
, Scene
).
94 nearest_object_intersecting_ray(
95 _Ray
, _NearestObj
, _Hit_location
, _Normal
, infinity
, []) ->
97 nearest_object_intersecting_ray(
98 _Ray
, NearestObj
, Hit_location
, Normal
, Distance
, []) ->
99 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
100 {NearestObj
, Distance
, Hit_location
, Normal
};
101 nearest_object_intersecting_ray(Ray
,
106 [CurrentObject
|Rest_of_scene
]) ->
107 NewDistance
= ray_object_intersect(Ray
, CurrentObject
),
108 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
109 if (NewDistance
/= infinity
)
110 and ((Distance
== infinity
) or (Distance
> NewDistance
)) ->
111 %io:format("another closer object found~n", []),
113 vector_add(Ray#ray
.origin
,
114 vector_scalar_mult(Ray#ray
.direction
, NewDistance
)),
115 New_normal
= object_normal_at_point(
116 CurrentObject
, New_hit_location
),
117 nearest_object_intersecting_ray(
125 %io:format("no closer obj found~n", []),
126 nearest_object_intersecting_ray(Ray
,
134 ray_object_intersect(Ray
, Object
) ->
137 ray_sphere_intersect(Ray
, Object
);
142 object_normal_at_point(#sphere
{center
=Center
}, Point
) ->
144 vector_sub(Point
, Center
)).
146 ray_sphere_intersect(
151 #sphere
{radius
=Radius
, center
=#vector
{
152 x
=Xc
, y
=Yc
, z
=Zc
}}) ->
153 A
= Xd
*Xd
+ Yd
*Yd
+ Zd
*Zd
,
154 B
= 2 * (Xd
*(X0
-Xc
) + Yd
*(Y0
-Yc
) + Zd
*(Z0
-Zc
)),
155 C
= (X0
-Xc
)*(X0
-Xc
) + (Y0
-Yc
)*(Y0
-Yc
) + (Z0
-Zc
)*(Z0
-Zc
) - Radius
*Radius
,
156 Discriminant
= B
*B
- 4*A
*C
,
157 %io:format("A=~w B=~w C=~w discriminant=~w~n",
158 % [A, B, C, Discriminant]),
159 if Discriminant
>= 0 ->
160 T0
= (-B
+ math:sqrt(Discriminant
))/2,
161 T1
= (-B
- math:sqrt(Discriminant
))/2,
162 if (T0
>= 0) and (T1
>= 0) ->
163 %io:format("T0=~w T1=~w~n", [T0, T1]),
174 focal_length(Angle
, Dimension
) ->
175 Dimension
/(2*math:tan(Angle
*(math:pi()/180)/2)).
177 point_on_screen(X
, Y
, Camera
) ->
178 %TODO: implement rotation (using quaternions)
179 Screen_width
= (Camera#camera
.screen
)#screen
.width
,
180 Screen_height
= (Camera#camera
.screen
)#screen
.height
,
181 lists:foldl(fun(Vect
, Sum
) -> vector_add(Vect
, Sum
) end,
182 Camera#camera
.location
,
184 #vector
{x
=0, y
=0, z
=1},
188 #vector
{x
= (X
-0.5) * Screen_width
,
192 y
= (Y
-0.5) * Screen_height
,
197 shoot_ray(From
, Through
) ->
198 #ray
{origin
=From
, direction
=vector_normalize(vector_sub(Through
, From
))}.
200 % assume that X and Y are percentages of the 3D world screen dimensions
201 ray_through_pixel(X
, Y
, Camera
) ->
202 shoot_ray(Camera#camera
.location
, point_on_screen(X
, Y
, Camera
)).
204 vectors_equal(V1
, V2
) ->
205 vectors_equal(V1
, V2
, 0.0001).
206 vectors_equal(V1
, V2
, Epsilon
) ->
207 (V1#vector
.x
+ Epsilon
>= V2#vector
.x
)
208 and (V1#vector
.x
- Epsilon
=<V2#vector
.x
)
209 and (V1#vector
.y
+ Epsilon
>= V2#vector
.y
)
210 and (V1#vector
.y
- Epsilon
=<V2#vector
.y
)
211 and (V1#vector
.z
+ Epsilon
>= V2#vector
.z
)
212 and (V1#vector
.z
- Epsilon
=<V2#vector
.z
).
215 vector_add(V1
, V2
) ->
216 #vector
{x
= V1#vector
.x
+ V2#vector
.x
,
217 y
= V1#vector
.y
+ V2#vector
.y
,
218 z
= V1#vector
.z
+ V2#vector
.z
}.
220 vector_sub(V1
, V2
) ->
221 #vector
{x
= V1#vector
.x
- V2#vector
.x
,
222 y
= V1#vector
.y
- V2#vector
.y
,
223 z
= V1#vector
.z
- V2#vector
.z
}.
225 vector_square_mag(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
229 math:sqrt(vector_square_mag(V
)).
231 vector_scalar_mult(#vector
{x
=X
, y
=Y
, z
=Z
}, Scalar
) ->
232 #vector
{x
=X
*Scalar
, y
=Y
*Scalar
, z
=Z
*Scalar
}.
234 vector_dot_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
235 A1
*B1
+ A2
*B2
+ A3
*B3
.
237 vector_cross_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
238 #vector
{x
= A2
*B3
- A3
*B2
,
242 vector_normalize(V
) ->
245 #vector
{x
=0, y
=0, z
=0};
247 vector_scalar_mult(V
, 1/vector_mag(V
))
250 vector_neg(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
251 #vector
{x
=-X
, y
=-Y
, z
=-Z
}.
253 vector_rotate(V1
, _V2
) ->
254 %TODO: implement using quaternions
257 object_colour(#sphere
{ colour
=C
}) ->
259 object_colour(_Unknown
) ->
262 point_on_sphere(#sphere
{radius
=Radius
, center
=#vector
{x
=XC
, y
=YC
, z
=ZC
}},
263 #vector
{x
=X
, y
=Y
, z
=Z
}) ->
265 Left_hand_side
= (X
-XC
)*(X
-XC
) + (Y
-YC
)*(Y
-YC
) + (Z
-ZC
)*(Z
-ZC
),
266 Right_hand_side
= Radius
*Radius
,
267 (Left_hand_side
+ Epsilon
>= Right_hand_side
) and (Left_hand_side
- Epsilon
=< Right_hand_side
).
269 colour_to_vector(#colour
{r
=R
, g
=G
, b
=B
}) ->
270 #vector
{x
=R
, y
=G
, z
=B
}.
271 vector_to_colour(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
272 #colour
{r
=X
, g
=Y
, b
=Z
}.
273 colour_to_pixel(#colour
{r
=R
, g
=G
, b
=B
}) ->
275 colour_trunc(#colour
{r
=R
, g
=G
, b
=B
}) ->
276 #colour
{r
=trunc(R
), g
=trunc(G
), b
=trunc(B
)}.
278 % returns a list of objects in the scene
279 % camera is assumed to be the first element in the scene
281 [#camera
{location
=#vector
{x
=0, y
=0, z
=0},
282 rotation
=#vector
{x
=0, y
=0, z
=0},
284 screen
=#screen
{width
=4, height
=3}},
285 #point_light
{colour
=#colour
{r
=255, g
=255, b
=128},
287 location
=#vector
{x
=5, y
=-2, z
=0}},
289 center
=#vector
{x
=0, y
=0, z
=7},
290 colour
=#colour
{r
=0, g
=128, b
=255}},
292 center
=#vector
{x
=-5, y
=3, z
=9},
293 colour
=#colour
{r
=255, g
=128, b
=0}},
295 center
=#vector
{x
=-5, y
=-2, z
=10},
296 colour
=#colour
{r
=128, g
=255, b
=0}}
300 % assumes Pixels are ordered in a row by row fasion
301 write_pixels_to_ppm(Width
, Height
, MaxValue
, Pixels
, Filename
) ->
302 case file:open(Filename
, write
) of
304 io:format("file opened~n", []),
305 io:format(IoDevice
, "P3~n", []),
306 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
307 io:format(IoDevice
, "~p~n", [MaxValue
]),
310 io:format(IoDevice
, "~p ~p ~p ",
313 file:close(IoDevice
),
314 io:format("done~n", []);
316 io:format("error opening file~n", [])
320 go(16, 12, "/tmp/traced.ppm").
321 go(Width
, Height
, Filename
) ->
322 write_pixels_to_ppm(Width
,
325 raytraced_pixel_list(Width
,
332 io:format("testing the scene function", []),
340 {colour
, 255, 255, 128},
346 {colour
, 0, 128, 255}},
350 {colour
, 255, 128, 0}},
353 {vector
, -5, -2, 10},
354 {colour
, 128, 255, 0}}] ->
361 io:format("this test always fails", []),
365 io:format("this test always passes", []),
369 Tests
= [fun scene_test
/0,
371 fun vector_equality_test
/0,
372 fun vector_addition_test
/0,
373 fun vector_subtraction_test
/0,
374 fun vector_square_mag_test
/0,
375 fun vector_mag_test
/0,
376 fun vector_scalar_multiplication_test
/0,
377 fun vector_dot_product_test
/0,
378 fun vector_cross_product_test
/0,
379 fun vector_normalization_test
/0,
380 fun vector_negation_test
/0,
381 fun ray_through_pixel_test
/0,
382 fun ray_shooting_test
/0,
383 fun point_on_screen_test
/0,
384 fun nearest_object_intersecting_ray_test
/0,
385 fun focal_length_test
/0,
386 fun vector_rotation_test
/0,
387 fun point_light_intensity_test
/0,
388 fun object_normal_at_point_test
/0
390 run_tests(Tests
, 1, true
).
392 run_tests([], _Num
, Success
) ->
395 io:format("Success!~n", []),
398 io:format("some tests failed~n", []),
402 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
403 io:format("test #~p: ", [Num
]),
404 Current_success
= First_test(),
405 case Current_success
of
407 io:format(" - OK~n", []);
409 io:format(" - FAILED~n", [])
411 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
413 vector_equality_test() ->
414 io:format("vector equality"),
415 Vector1
= #vector
{x
=0, y
=0, z
=0},
416 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
417 Vector3
= #vector
{x
=0.0983, y
=0.0214, z
=0.12342},
418 Vector4
= #vector
{x
=0.0984, y
=0.0213, z
=0.12341},
419 Vector5
= #vector
{x
=10/3, y
=-10/6, z
=8/7},
420 Vector6
= #vector
{x
=3.3, y
=-1.6, z
=1.1},
422 Subtest1
= vectors_equal(Vector1
, Vector1
)
423 and
vectors_equal(Vector2
, Vector2
)
424 and
not (vectors_equal(Vector1
, Vector2
))
425 and
not (vectors_equal(Vector2
, Vector1
)),
426 Subtest2
= vectors_equal(Vector3
, Vector4
, 0.0001),
427 Subtest3
= vectors_equal(Vector5
, Vector6
, 0.1),
429 Subtest1 and Subtest2 and Subtest3
.
432 vector_addition_test() ->
433 io:format("vector addition", []),
434 Vector0
= vector_add(
435 #vector
{x
=3, y
=7, z
=-3},
436 #vector
{x
=0, y
=-24, z
=123}),
437 Subtest1
= (Vector0#vector
.x
== 3)
438 and (Vector0#vector
.y
== -17)
439 and (Vector0#vector
.z
== 120),
441 Vector1
= #vector
{x
=5, y
=0, z
=984},
442 Vector2
= vector_add(Vector1
, Vector1
),
443 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
444 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
445 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
447 Vector3
= #vector
{x
=908, y
=-098, z
=234},
448 Vector4
= vector_add(Vector3
, #vector
{x
=0, y
=0, z
=0}),
449 Subtest3
= vectors_equal(Vector3
, Vector4
),
451 Subtest1 and Subtest2 and Subtest3
.
453 vector_subtraction_test() ->
454 io:format("vector subtraction", []),
455 Vector1
= #vector
{x
=0, y
=0, z
=0},
456 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
457 Vector3
= #vector
{x
=1, y
=1, z
=1},
458 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
460 Subtest1
= vectors_equal(Vector1
, vector_sub(Vector1
, Vector1
)),
461 Subtest2
= vectors_equal(Vector3
, vector_sub(Vector3
, Vector1
)),
462 Subtest3
= not
vectors_equal(Vector3
, vector_sub(Vector1
, Vector3
)),
463 Subtest4
= vectors_equal(Vector4
, vector_sub(Vector4
, Vector1
)),
464 Subtest5
= not
vectors_equal(Vector4
, vector_sub(Vector1
, Vector4
)),
465 Subtest5
= vectors_equal(vector_add(Vector2
, Vector4
),
466 vector_sub(Vector2
, Vector3
)),
468 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
470 vector_square_mag_test() ->
471 io:format("vector square magnitude test", []),
472 Vector1
= #vector
{x
=0, y
=0, z
=0},
473 Vector2
= #vector
{x
=1, y
=1, z
=1},
474 Vector3
= #vector
{x
=3, y
=-4, z
=0},
476 Subtest1
= (0 == vector_square_mag(Vector1
)),
477 Subtest2
= (3 == vector_square_mag(Vector2
)),
478 Subtest3
= (25 == vector_square_mag(Vector3
)),
480 Subtest1 and Subtest2 and Subtest3
.
483 io:format("vector magnitude test", []),
484 Vector1
= #vector
{x
=0, y
=0, z
=0},
485 Vector2
= #vector
{x
=1, y
=1, z
=1},
486 Vector3
= #vector
{x
=3, y
=-4, z
=0},
488 Subtest1
= (0 == vector_mag(Vector1
)),
489 Subtest2
= (math:sqrt(3) == vector_mag(Vector2
)),
490 Subtest3
= (5 == vector_mag(Vector3
)),
492 Subtest1 and Subtest2 and Subtest3
.
494 vector_scalar_multiplication_test() ->
495 io:format("scalar multiplication test", []),
496 Vector1
= #vector
{x
=0, y
=0, z
=0},
497 Vector2
= #vector
{x
=1, y
=1, z
=1},
498 Vector3
= #vector
{x
=3, y
=-4, z
=0},
500 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
501 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
502 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
503 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
504 vector_scalar_mult(Vector2
, 4)),
505 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
506 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
508 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
510 vector_dot_product_test() ->
511 io:format("dot product test", []),
512 Vector1
= #vector
{x
=1, y
=3, z
=-5},
513 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
514 Vector3
= #vector
{x
=0, y
=0, z
=0},
515 Vector4
= #vector
{x
=1, y
=0, z
=0},
516 Vector5
= #vector
{x
=0, y
=1, z
=0},
518 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
519 Subtest2
= vector_dot_product(Vector2
, Vector2
)
520 == vector_square_mag(Vector2
),
521 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
522 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
524 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
526 vector_cross_product_test() ->
527 io:format("cross product test", []),
528 Vector1
= #vector
{x
=0, y
=0, z
=0},
529 Vector2
= #vector
{x
=1, y
=0, z
=0},
530 Vector3
= #vector
{x
=0, y
=1, z
=0},
531 Vector4
= #vector
{x
=0, y
=0, z
=1},
532 Vector5
= #vector
{x
=1, y
=2, z
=3},
533 Vector6
= #vector
{x
=4, y
=5, z
=6},
534 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
535 Vector8
= #vector
{x
=-1, y
=0, z
=0},
536 Vector9
= #vector
{x
=-9, y
=8, z
=433},
538 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
539 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
540 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
541 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
542 Subtest5
= vectors_equal(
543 vector_cross_product(Vector7
,
544 vector_add(Vector8
, Vector9
)),
546 vector_cross_product(Vector7
, Vector8
),
547 vector_cross_product(Vector7
, Vector9
))),
548 Subtest6
= vectors_equal(Vector1
,
551 vector_cross_product(
553 vector_cross_product(Vector8
, Vector9
)),
554 vector_cross_product(
556 vector_cross_product(Vector9
, Vector7
))),
557 vector_cross_product(
559 vector_cross_product(Vector7
, Vector8
)))),
561 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
563 vector_normalization_test() ->
564 io:format("normalization test", []),
565 Vector1
= #vector
{x
=0, y
=0, z
=0},
566 Vector2
= #vector
{x
=1, y
=0, z
=0},
567 Vector3
= #vector
{x
=5, y
=0, z
=0},
569 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
570 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
571 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
572 Subtest4
= vectors_equal(Vector2
, vector_normalize(
573 vector_scalar_mult(Vector2
, 324))),
575 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
577 vector_negation_test() ->
578 io:format("vector negation test", []),
579 Vector1
= #vector
{x
=0, y
=0, z
=0},
580 Vector2
= #vector
{x
=4, y
=-5, z
=6},
582 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
583 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
585 Subtest1 and Subtest2
.
587 ray_through_pixel_test() ->
588 io:format("ray through pixel test", []),
591 ray_shooting_test() ->
592 io:format("ray shooting test"),
593 Vector1
= #vector
{x
=0, y
=0, z
=0},
594 Vector2
= #vector
{x
=1, y
=0, z
=0},
596 Subtest1
= vectors_equal(
597 (shoot_ray(Vector1
, Vector2
))#ray
.direction
,
602 ray_sphere_intersection_test() ->
605 center
=#vector
{x
= 0, y
=0, z
=10},
606 colour
=#colour
{r
=111, g
=111, b
=111}},
608 origin
=#vector
{x
=0, y
=0, z
=0},
609 direction
=#vector
{x
=0, y
=0, z
=1}},
611 origin
=#vector
{x
=3, y
=0, z
=0},
612 direction
=#vector
{x
=0, y
=0, z
=1}},
614 origin
=#vector
{x
=4, y
=0, z
=0},
615 direction
=#vector
{x
=0, y
=0, z
=1}},
616 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray1
, Sphere
)]),
617 Subtest1
= ray_sphere_intersect(Ray1
, Sphere
) == 7.0,
618 Subtest2
= ray_sphere_intersect(Ray2
, Sphere
) == 10.0,
619 Subtest3
= ray_sphere_intersect(Ray3
, Sphere
) == none
,
620 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray2
, Sphere
)]),
621 io:format("ray/sphere intersection=~w~n", [ray_sphere_intersect(Ray3
, Sphere
)]),
622 Subtest1 and Subtest2 and Subtest3
.
624 point_on_screen_test() ->
625 io:format("point on screen test", []),
626 Camera1
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
627 rotation
=#vector
{x
=0, y
=0, z
=0},
629 screen
=#screen
{width
=1, height
=1}},
630 Camera2
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
631 rotation
=#vector
{x
=0, y
=0, z
=0},
633 screen
=#screen
{width
=640, height
=480}},
635 Subtest1
= vectors_equal(
636 #vector
{x
=0, y
=0, z
=0.5},
637 point_on_screen(0.5, 0.5, Camera1
)),
638 Subtest2
= vectors_equal(
639 #vector
{x
=-0.5, y
=-0.5, z
=0.5},
640 point_on_screen(0, 0, Camera1
)),
641 Subtest3
= vectors_equal(
642 #vector
{x
=0.5, y
=0.5, z
=0.5},
643 point_on_screen(1, 1, Camera1
)),
644 Subtest4
= vectors_equal(
645 point_on_screen(0, 0, Camera2
),
646 #vector
{x
=-320, y
=-240, z
=320}),
647 Subtest5
= vectors_equal(
648 point_on_screen(1, 1, Camera2
),
649 #vector
{x
=320, y
=240, z
=320}),
650 Subtest6
= vectors_equal(
651 point_on_screen(0.5, 0.5, Camera2
),
652 #vector
{x
=0, y
=0, z
=320}),
654 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
656 nearest_object_intersecting_ray_test() ->
657 io:format("nearest object intersecting ray test", []),
658 % test to make sure that we really get the closest object
659 Sphere1
=#sphere
{radius
=5,
660 center
=#vector
{x
=0, y
=0, z
=10},
661 colour
=#colour
{r
=0, g
=0, b
=10}},
662 Sphere2
=#sphere
{radius
=5,
663 center
=#vector
{x
=0, y
=0, z
=20},
664 colour
=#colour
{r
=0, g
=0, b
=20}},
665 Sphere3
=#sphere
{radius
=5,
666 center
=#vector
{x
=0, y
=0, z
=30},
667 colour
=#colour
{r
=0, g
=0, b
=30}},
668 Sphere4
=#sphere
{radius
=5,
669 center
=#vector
{x
=0, y
=0, z
=-10},
670 colour
=#colour
{r
=0, g
=0, b
=-10}},
671 Scene1
=[Sphere1
, Sphere2
, Sphere3
, Sphere4
],
672 Ray1
=#ray
{origin
=#vector
{x
=0, y
=0, z
=0},
673 direction
=#vector
{x
=0, y
=0, z
=1}},
675 {Object1
, Distance1
, Hit_location
, Normal
} = nearest_object_intersecting_ray(
677 Subtest1
= (Object1
== Sphere1
) and (Distance1
== 5)
678 and
vectors_equal(Normal
, vector_neg(Ray1#ray
.direction
))
679 and
point_on_sphere(Sphere1
, Hit_location
),
683 focal_length_test() ->
686 io:format("focal length test", []),
688 fun({Focal_length
, Dimension
}, Matches
) ->
689 %Result = focal_length(Dimension, Size),
690 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
692 and ((Focal_length
+ Epsilon
>= focal_length(
694 and (Focal_length
- Epsilon
=< focal_length(
697 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
699 vector_rotation_test() ->
700 io:format("vector rotation test", []),
701 Vector1
= #vector
{x
=0, y
=0, z
=0},
702 Vector2
= #vector
{x
=0, y
=1, z
=0},
703 Vector3
= #vector
{x
=90, y
=0, z
=0},
704 Vector4
= #vector
{x
=45, y
=0, z
=0},
705 Vector5
= #vector
{x
=30.11, y
=-988.2, z
=92.231},
706 Vector6
= #vector
{x
=0, y
=0, z
=1},
708 Subtest1
= vectors_equal(
709 vector_rotate(Vector1
, Vector1
),
711 Subtest2
= vectors_equal(
712 vector_rotate(Vector5
, Vector1
),
714 Subtest3
= vectors_equal(
716 vector_rotate(Vector5
, Vector4
),
718 vector_rotate(Vector5
, Vector3
)),
719 Subtest4
= vectors_equal(
721 vector_rotate(Vector2
, Vector3
)),
723 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
725 point_light_intensity_test() ->
726 io:format("point light intensity test", []),
727 Light1
= #point_light
{colour
=#colour
{r
=255, g
=255, b
=200},
729 location
=#vector
{x
=0, y
=0, z
=0}},
730 Hit_normal1
= #vector
{x
=1, y
=0, z
=0},
731 Hit_location1
= #vector
{x
=-1, y
=0, z
=0},
733 Subtest1
= point_light_intensity(Light1
, Hit_normal1
, Hit_location1
) == #colour
{r
=255, g
=255, b
=200},
737 object_normal_at_point_test() ->
738 io:format("object normal at point test"),
739 Sphere1
= #sphere
{radius
=13.5,
740 center
=#vector
{x
=0, y
=0, z
=0},
741 colour
=#colour
{r
=0, g
=0, b
=0}},
742 Point1
= #vector
{x
=13.5, y
=0, z
=0},
743 Point2
= #vector
{x
=0, y
=13.5, z
=0},
744 Point3
= #vector
{x
=0, y
=0, z
=13.5},
745 Point4
= vector_neg(Point1
),
746 Point5
= vector_neg(Point2
),
747 Point6
= vector_neg(Point3
),
749 % sphere object tests
750 Subtest1
= vectors_equal(
751 vector_normalize(Point1
),
752 object_normal_at_point(Sphere1
, Point1
)),
753 Subtest2
= vectors_equal(
754 vector_normalize(Point2
),
755 object_normal_at_point(Sphere1
, Point2
)),
756 Subtest3
= vectors_equal(
757 vector_normalize(Point3
),
758 object_normal_at_point(Sphere1
, Point3
)),
759 Subtest4
= vectors_equal(
760 vector_normalize(Point4
),
761 object_normal_at_point(Sphere1
, Point4
)),
762 Subtest5
= vectors_equal(
763 vector_normalize(Point5
),
764 object_normal_at_point(Sphere1
, Point5
)),
765 Subtest6
= vectors_equal(
766 vector_normalize(Point6
),
767 object_normal_at_point(Sphere1
, Point6
)),
768 Subtest7
= not
vectors_equal(
769 vector_normalize(Point1
),
770 object_normal_at_point(Sphere1
, Point4
)),
772 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6