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(axis_aligned_cube, {size, location}).
12 raytraced_pixel_list(0, 0, _
) ->
14 raytraced_pixel_list(Width
, Height
, Scene
) when Width
> 0, Height
> 0 ->
19 % coordinates passed as a percentage
20 trace_ray_from_pixel({X
/Width
, Y
/Height
}, Scene
) end,
21 lists:seq(0, Height
- 1)) end,
22 lists:seq(0, Width
- 1)).
24 trace_ray_from_pixel({X
, Y
}, [Camera
|Rest_of_scene
]) ->
25 Ray
= ray_through_pixel(X
, Y
, Camera
),
26 {_Nearest_object
, _Distance
} = nearest_object_intersecting_ray(Ray
, Rest_of_scene
),
27 % return the Nearest_object's colour
28 {random:uniform(256)-1, random:uniform(256)-1, random:uniform(256)-1}.
30 nearest_object_intersecting_ray(_Ray
, _Scene
) ->
33 focal_length(Angle
, Dimension
) ->
34 Dimension
/(2*math:tan(Angle
*(math:pi()/180)/2)).
36 point_on_screen(_X
, _Y
, _Camera
) ->
39 shoot_ray(_From
, _Through
) ->
42 % assume that X and Y are percentages of the 3D world screen dimensions
43 ray_through_pixel(X
, Y
, Camera
) ->
44 shoot_ray(Camera#camera
.location
, point_on_screen(X
, Y
, Camera
)).
46 vectors_equal(V1
, V2
) ->
47 (V1#vector
.x
== V2#vector
.x
)
48 and (V1#vector
.y
== V2#vector
.y
)
49 and (V1#vector
.z
== V2#vector
.z
).
52 #vector
{x
= V1#vector
.x
+ V2#vector
.x
,
53 y
= V1#vector
.y
+ V2#vector
.y
,
54 z
= V1#vector
.z
+ V2#vector
.z
}.
57 #vector
{x
= V1#vector
.x
- V2#vector
.x
,
58 y
= V1#vector
.y
- V2#vector
.y
,
59 z
= V1#vector
.z
- V2#vector
.z
}.
61 vector_square_mag(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
65 math:sqrt(vector_square_mag(V
)).
67 vector_scalar_mult(#vector
{x
=X
, y
=Y
, z
=Z
}, Scalar
) ->
68 #vector
{x
=X
*Scalar
, y
=Y
*Scalar
, z
=Z
*Scalar
}.
70 vector_dot_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
71 A1
*B1
+ A2
*B2
+ A3
*B3
.
73 vector_cross_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
74 #vector
{x
= A2
*B3
- A3
*B2
,
78 vector_normalize(V
) ->
81 #vector
{x
=0, y
=0, z
=0};
83 vector_scalar_mult(V
, 1/vector_mag(V
))
86 vector_neg(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
87 #vector
{x
=-X
, y
=-Y
, z
=-Z
}.
89 % returns a list of objects in the scene
90 % camera is assumed to be the first element in the scene
92 [#camera
{location
=#vector
{x
=0, y
=0, z
=0},
93 rotation
=#vector
{x
=1, y
=0, z
=0},
95 screen
=#screen
{width
=1, height
=1}},
97 center
=#vector
{x
=5, y
=0, z
=0},
98 colour
=#colour
{r
=0, g
=128, b
=255}}
102 write_pixels_to_ppm(Width
, Height
, Pixels
, MaxValue
, Filename
) ->
103 case file:open(Filename
, write
) of
105 io:format("file opened~n", []),
106 io:format(IoDevice
, "P3~n", []),
107 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
108 io:format(IoDevice
, "~p~n", [MaxValue
]),
111 io:format(IoDevice
, "~p ~p ~p ",
114 file:close(IoDevice
),
115 io:format("done~n", []);
117 io:format("error opening file~n", [])
121 go(1, 1, "/tmp/traced.ppm").
122 go(Width
, Height
, Filename
) ->
123 write_pixels_to_ppm(Width
,
126 raytraced_pixel_list(Width
,
133 io:format("testing the scene function", []),
143 {colour
, 0, 128, 255}}] ->
150 io:format("this test always fails", []),
154 io:format("this test always passes", []),
158 Tests
= [fun scene_test
/0,
160 fun vector_equality_test
/0,
161 fun vector_addition_test
/0,
162 fun vector_subtraction_test
/0,
163 fun vector_square_mag_test
/0,
164 fun vector_mag_test
/0,
165 fun vector_scalar_multiplication_test
/0,
166 fun vector_dot_product_test
/0,
167 fun vector_cross_product_test
/0,
168 fun vector_normalization_test
/0,
169 fun vector_negation_test
/0,
170 fun ray_through_pixel_test
/0,
171 fun ray_shooting_test
/0,
172 fun point_on_screen_test
/0,
173 fun nearest_object_intersecting_ray_test
/0,
174 fun focal_length_test
/0
176 run_tests(Tests
, 1, true
).
178 run_tests([], _Num
, Success
) ->
181 io:format("Success!~n", []),
184 io:format("some tests failed~n", []),
188 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
189 io:format("test #~p: ", [Num
]),
190 Current_success
= First_test(),
191 case Current_success
of
193 io:format(" - OK~n", []);
195 io:format(" - FAILED~n", [])
197 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
199 vector_equality_test() ->
200 io:format("vector equality"),
201 Vector1
= #vector
{x
=0, y
=0, z
=0},
202 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
204 vectors_equal(Vector1
, Vector1
)
205 and
vectors_equal(Vector2
, Vector2
)
206 and
not (vectors_equal(Vector1
, Vector2
))
207 and
not (vectors_equal(Vector2
, Vector1
)).
210 vector_addition_test() ->
211 io:format("vector addition", []),
212 Vector0
= vector_add(
213 #vector
{x
=3, y
=7, z
=-3},
214 #vector
{x
=0, y
=-24, z
=123}),
215 Subtest1
= (Vector0#vector
.x
== 3)
216 and (Vector0#vector
.y
== -17)
217 and (Vector0#vector
.z
== 120),
219 Vector1
= #vector
{x
=5, y
=0, z
=984},
220 Vector2
= vector_add(Vector1
, Vector1
),
221 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
222 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
223 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
225 Vector3
= #vector
{x
=908, y
=-098, z
=234},
226 Vector4
= vector_add(Vector3
, #vector
{x
=0, y
=0, z
=0}),
227 Subtest3
= vectors_equal(Vector3
, Vector4
),
229 Subtest1 and Subtest2 and Subtest3
.
231 vector_subtraction_test() ->
232 io:format("vector subtraction", []),
233 Vector1
= #vector
{x
=0, y
=0, z
=0},
234 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
235 Vector3
= #vector
{x
=1, y
=1, z
=1},
236 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
238 Subtest1
= vectors_equal(Vector1
, vector_sub(Vector1
, Vector1
)),
239 Subtest2
= vectors_equal(Vector3
, vector_sub(Vector3
, Vector1
)),
240 Subtest3
= not
vectors_equal(Vector3
, vector_sub(Vector1
, Vector3
)),
241 Subtest4
= vectors_equal(Vector4
, vector_sub(Vector4
, Vector1
)),
242 Subtest5
= not
vectors_equal(Vector4
, vector_sub(Vector1
, Vector4
)),
243 Subtest5
= vectors_equal(vector_add(Vector2
, Vector4
),
244 vector_sub(Vector2
, Vector3
)),
246 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
248 vector_square_mag_test() ->
249 io:format("vector square magnitude test", []),
250 Vector1
= #vector
{x
=0, y
=0, z
=0},
251 Vector2
= #vector
{x
=1, y
=1, z
=1},
252 Vector3
= #vector
{x
=3, y
=-4, z
=0},
254 Subtest1
= (0 == vector_square_mag(Vector1
)),
255 Subtest2
= (3 == vector_square_mag(Vector2
)),
256 Subtest3
= (25 == vector_square_mag(Vector3
)),
258 Subtest1 and Subtest2 and Subtest3
.
261 io:format("vector magnitude test", []),
262 Vector1
= #vector
{x
=0, y
=0, z
=0},
263 Vector2
= #vector
{x
=1, y
=1, z
=1},
264 Vector3
= #vector
{x
=3, y
=-4, z
=0},
266 Subtest1
= (0 == vector_mag(Vector1
)),
267 Subtest2
= (math:sqrt(3) == vector_mag(Vector2
)),
268 Subtest3
= (5 == vector_mag(Vector3
)),
270 Subtest1 and Subtest2 and Subtest3
.
272 vector_scalar_multiplication_test() ->
273 io:format("scalar multiplication test", []),
274 Vector1
= #vector
{x
=0, y
=0, z
=0},
275 Vector2
= #vector
{x
=1, y
=1, z
=1},
276 Vector3
= #vector
{x
=3, y
=-4, z
=0},
278 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
279 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
280 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
281 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
282 vector_scalar_mult(Vector2
, 4)),
283 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
284 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
286 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
288 vector_dot_product_test() ->
289 io:format("dot product test", []),
290 Vector1
= #vector
{x
=1, y
=3, z
=-5},
291 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
292 Vector3
= #vector
{x
=0, y
=0, z
=0},
293 Vector4
= #vector
{x
=1, y
=0, z
=0},
294 Vector5
= #vector
{x
=0, y
=1, z
=0},
296 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
297 Subtest2
= vector_dot_product(Vector2
, Vector2
)
298 == vector_square_mag(Vector2
),
299 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
300 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
302 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
304 vector_cross_product_test() ->
305 io:format("cross product test", []),
306 Vector1
= #vector
{x
=0, y
=0, z
=0},
307 Vector2
= #vector
{x
=1, y
=0, z
=0},
308 Vector3
= #vector
{x
=0, y
=1, z
=0},
309 Vector4
= #vector
{x
=0, y
=0, z
=1},
310 Vector5
= #vector
{x
=1, y
=2, z
=3},
311 Vector6
= #vector
{x
=4, y
=5, z
=6},
312 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
313 Vector8
= #vector
{x
=-1, y
=0, z
=0},
314 Vector9
= #vector
{x
=-9, y
=8, z
=433},
316 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
317 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
318 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
319 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
320 Subtest5
= vectors_equal(
321 vector_cross_product(Vector7
,
322 vector_add(Vector8
, Vector9
)),
324 vector_cross_product(Vector7
, Vector8
),
325 vector_cross_product(Vector7
, Vector9
))),
326 Subtest6
= vectors_equal(Vector1
,
329 vector_cross_product(
331 vector_cross_product(Vector8
, Vector9
)),
332 vector_cross_product(
334 vector_cross_product(Vector9
, Vector7
))),
335 vector_cross_product(
337 vector_cross_product(Vector7
, Vector8
)))),
339 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
341 vector_normalization_test() ->
342 io:format("normalization test", []),
343 Vector1
= #vector
{x
=0, y
=0, z
=0},
344 Vector2
= #vector
{x
=1, y
=0, z
=0},
345 Vector3
= #vector
{x
=5, y
=0, z
=0},
347 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
348 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
349 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
350 Subtest4
= vectors_equal(Vector2
, vector_normalize(
351 vector_scalar_mult(Vector2
, 324))),
353 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
355 vector_negation_test() ->
356 io:format("vector negation test", []),
357 Vector1
= #vector
{x
=0, y
=0, z
=0},
358 Vector2
= #vector
{x
=4, y
=-5, z
=6},
360 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
361 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
363 Subtest1 and Subtest2
.
365 ray_through_pixel_test() ->
366 io:format("ray through pixel test", []),
369 ray_shooting_test() ->
370 io:format("ray shooting test", []),
373 point_on_screen_test() ->
374 io:format("point on screen test", []),
375 Camera1
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
376 rotation
=#vector
{x
=0, y
=0, z
=0},
378 screen
=#screen
{width
=1, height
=1}},
381 nearest_object_intersecting_ray_test() ->
382 io:format("nearest object intersecting ray test", []),
385 focal_length_test() ->
388 io:format("focal length test", []),
390 fun({Focal_length
, Dimension
}, Matches
) ->
391 Result
= focal_length(Dimension
, Size
),
392 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
394 and ((Focal_length
+ Epsilon
>= focal_length(
396 and (Focal_length
- Epsilon
=< focal_length(
399 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).