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 point_on_screen(_X
, _Y
, _Camera
) ->
36 shoot_ray(_From
, _Through
) ->
39 % assume that X and Y are percentages of the 3D world screen dimensions
40 ray_through_pixel(X
, Y
, Camera
) ->
41 shoot_ray(Camera#camera
.location
, point_on_screen(X
, Y
, Camera
)).
43 vectors_equal(V1
, V2
) ->
44 (V1#vector
.x
== V2#vector
.x
)
45 and (V1#vector
.y
== V2#vector
.y
)
46 and (V1#vector
.z
== V2#vector
.z
).
48 add_vectors(V1
, V2
) ->
49 #vector
{x
= V1#vector
.x
+ V2#vector
.x
,
50 y
= V1#vector
.y
+ V2#vector
.y
,
51 z
= V1#vector
.z
+ V2#vector
.z
}.
53 subtract_vectors(V1
, V2
) ->
54 #vector
{x
= V1#vector
.x
- V2#vector
.x
,
55 y
= V1#vector
.y
- V2#vector
.y
,
56 z
= V1#vector
.z
- V2#vector
.z
}.
58 vector_square_mag(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
61 vector_magnitude(V
) ->
62 math:sqrt(vector_square_mag(V
)).
64 vector_scalar_mult(#vector
{x
=X
, y
=Y
, z
=Z
}, Scalar
) ->
65 #vector
{x
=X
*Scalar
, y
=Y
*Scalar
, z
=Z
*Scalar
}.
67 vector_dot_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
68 A1
*B1
+ A2
*B2
+ A3
*B3
.
70 vector_cross_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
71 #vector
{x
= A2
*B3
- A3
*B2
,
75 vector_normalize(V
) ->
76 Mag
= vector_magnitude(V
),
78 #vector
{x
=0, y
=0, z
=0};
80 vector_scalar_mult(V
, 1/vector_magnitude(V
))
83 vector_neg(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
84 #vector
{x
=-X
, y
=-Y
, z
=-Z
}.
86 % returns a list of objects in the scene
87 % camera is assumed to be the first element in the scene
89 [#camera
{location
=#vector
{x
=0, y
=0, z
=0},
90 rotation
=#vector
{x
=1, y
=0, z
=0},
92 screen
=#screen
{width
=1, height
=1}},
94 center
=#vector
{x
=5, y
=0, z
=0},
95 colour
=#colour
{r
=0, g
=128, b
=255}}
99 write_pixels_to_ppm(Width
, Height
, Pixels
, MaxValue
, Filename
) ->
100 case file:open(Filename
, write
) of
102 io:format("file opened~n", []),
103 io:format(IoDevice
, "P3~n", []),
104 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
105 io:format(IoDevice
, "~p~n", [MaxValue
]),
108 io:format(IoDevice
, "~p ~p ~p ",
111 file:close(IoDevice
),
112 io:format("done~n", []);
114 io:format("error opening file~n", [])
118 go(1, 1, "/tmp/traced.ppm").
119 go(Width
, Height
, Filename
) ->
120 write_pixels_to_ppm(Width
,
123 raytraced_pixel_list(Width
,
130 io:format("testing the scene function", []),
140 {colour
, 0, 128, 255}}] ->
147 io:format("this test always fails", []),
151 io:format("this test always passes", []),
155 Tests
= [fun scene_test
/0,
157 fun vector_equality_test
/0,
158 fun vector_addition_test
/0,
159 fun vector_subtraction_test
/0,
160 fun vector_square_mag_test
/0,
161 fun vector_magnitude_test
/0,
162 fun vector_scalar_multiplication_test
/0,
163 fun vector_dot_product_test
/0,
164 fun vector_cross_product_test
/0,
165 fun vector_normalization_test
/0,
166 fun vector_negation_test
/0,
167 fun ray_through_pixel_test
/0,
168 fun ray_shooting_test
/0,
169 fun point_on_screen_test
/0,
170 fun nearest_object_intersecting_ray_test
/0
172 run_tests(Tests
, 1, true
).
174 run_tests([], _Num
, Success
) ->
177 io:format("Success!~n", []),
180 io:format("some tests failed~n", []),
184 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
185 io:format("test #~p: ", [Num
]),
186 Current_success
= First_test(),
187 case Current_success
of
189 io:format(" - OK~n", []);
191 io:format(" - FAILED~n", [])
193 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
195 vector_equality_test() ->
196 io:format("vector equality"),
197 Vector1
= #vector
{x
=0, y
=0, z
=0},
198 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
200 vectors_equal(Vector1
, Vector1
)
201 and
vectors_equal(Vector2
, Vector2
)
202 and
not (vectors_equal(Vector1
, Vector2
))
203 and
not (vectors_equal(Vector2
, Vector1
)).
206 vector_addition_test() ->
207 io:format("vector addition", []),
208 Vector0
= add_vectors(
209 #vector
{x
=3, y
=7, z
=-3},
210 #vector
{x
=0, y
=-24, z
=123}),
211 Subtest1
= (Vector0#vector
.x
== 3)
212 and (Vector0#vector
.y
== -17)
213 and (Vector0#vector
.z
== 120),
215 Vector1
= #vector
{x
=5, y
=0, z
=984},
216 Vector2
= add_vectors(Vector1
, Vector1
),
217 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
218 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
219 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
221 Vector3
= #vector
{x
=908, y
=-098, z
=234},
222 Vector4
= add_vectors(Vector3
, #vector
{x
=0, y
=0, z
=0}),
223 Subtest3
= vectors_equal(Vector3
, Vector4
),
225 Subtest1 and Subtest2 and Subtest3
.
227 vector_subtraction_test() ->
228 io:format("vector subtraction", []),
229 Vector1
= #vector
{x
=0, y
=0, z
=0},
230 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
231 Vector3
= #vector
{x
=1, y
=1, z
=1},
232 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
234 Subtest1
= vectors_equal(Vector1
, subtract_vectors(Vector1
, Vector1
)),
235 Subtest2
= vectors_equal(Vector3
, subtract_vectors(Vector3
, Vector1
)),
236 Subtest3
= not
vectors_equal(Vector3
, subtract_vectors(Vector1
, Vector3
)),
237 Subtest4
= vectors_equal(Vector4
, subtract_vectors(Vector4
, Vector1
)),
238 Subtest5
= not
vectors_equal(Vector4
, subtract_vectors(Vector1
, Vector4
)),
239 Subtest5
= vectors_equal(add_vectors(Vector2
, Vector4
),
240 subtract_vectors(Vector2
, Vector3
)),
242 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
244 vector_square_mag_test() ->
245 io:format("vector square magnitude test", []),
246 Vector1
= #vector
{x
=0, y
=0, z
=0},
247 Vector2
= #vector
{x
=1, y
=1, z
=1},
248 Vector3
= #vector
{x
=3, y
=-4, z
=0},
250 Subtest1
= (0 == vector_square_mag(Vector1
)),
251 Subtest2
= (3 == vector_square_mag(Vector2
)),
252 Subtest3
= (25 == vector_square_mag(Vector3
)),
254 Subtest1 and Subtest2 and Subtest3
.
256 vector_magnitude_test() ->
257 io:format("vector magnitude test", []),
258 Vector1
= #vector
{x
=0, y
=0, z
=0},
259 Vector2
= #vector
{x
=1, y
=1, z
=1},
260 Vector3
= #vector
{x
=3, y
=-4, z
=0},
262 Subtest1
= (0 == vector_magnitude(Vector1
)),
263 Subtest2
= (math:sqrt(3) == vector_magnitude(Vector2
)),
264 Subtest3
= (5 == vector_magnitude(Vector3
)),
266 Subtest1 and Subtest2 and Subtest3
.
268 vector_scalar_multiplication_test() ->
269 io:format("scalar multiplication test", []),
270 Vector1
= #vector
{x
=0, y
=0, z
=0},
271 Vector2
= #vector
{x
=1, y
=1, z
=1},
272 Vector3
= #vector
{x
=3, y
=-4, z
=0},
274 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
275 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
276 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
277 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
278 vector_scalar_mult(Vector2
, 4)),
279 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
280 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
282 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
284 vector_dot_product_test() ->
285 io:format("dot product test", []),
286 Vector1
= #vector
{x
=1, y
=3, z
=-5},
287 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
288 Vector3
= #vector
{x
=0, y
=0, z
=0},
289 Vector4
= #vector
{x
=1, y
=0, z
=0},
290 Vector5
= #vector
{x
=0, y
=1, z
=0},
292 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
293 Subtest2
= vector_dot_product(Vector2
, Vector2
)
294 == vector_square_mag(Vector2
),
295 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
296 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
298 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
300 vector_cross_product_test() ->
301 io:format("cross product test", []),
302 Vector1
= #vector
{x
=0, y
=0, z
=0},
303 Vector2
= #vector
{x
=1, y
=0, z
=0},
304 Vector3
= #vector
{x
=0, y
=1, z
=0},
305 Vector4
= #vector
{x
=0, y
=0, z
=1},
306 Vector5
= #vector
{x
=1, y
=2, z
=3},
307 Vector6
= #vector
{x
=4, y
=5, z
=6},
308 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
309 Vector8
= #vector
{x
=-1, y
=0, z
=0},
310 Vector9
= #vector
{x
=-9, y
=8, z
=433},
312 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
313 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
314 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
315 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
316 Subtest5
= vectors_equal(
317 vector_cross_product(Vector7
,
318 add_vectors(Vector8
, Vector9
)),
320 vector_cross_product(Vector7
, Vector8
),
321 vector_cross_product(Vector7
, Vector9
))),
322 Subtest6
= vectors_equal(Vector1
,
325 vector_cross_product(
327 vector_cross_product(Vector8
, Vector9
)),
328 vector_cross_product(
330 vector_cross_product(Vector9
, Vector7
))),
331 vector_cross_product(
333 vector_cross_product(Vector7
, Vector8
)))),
335 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
337 vector_normalization_test() ->
338 io:format("normalization test", []),
339 Vector1
= #vector
{x
=0, y
=0, z
=0},
340 Vector2
= #vector
{x
=1, y
=0, z
=0},
341 Vector3
= #vector
{x
=5, y
=0, z
=0},
343 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
344 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
345 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
346 Subtest4
= vectors_equal(Vector2
, vector_normalize(
347 vector_scalar_mult(Vector2
, 324))),
349 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
351 vector_negation_test() ->
352 io:format("vector negation test", []),
353 Vector1
= #vector
{x
=0, y
=0, z
=0},
354 Vector2
= #vector
{x
=4, y
=-5, z
=6},
356 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
357 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
359 Subtest1 and Subtest2
.
361 ray_through_pixel_test() ->
362 io:format("ray through pixel test", []),
365 ray_shooting_test() ->
366 io:format("ray shooting test", []),
369 point_on_screen_test() ->
370 io:format("point on screen test", []),
373 nearest_object_intersecting_ray_test() ->
374 io:format("nearest object intersecting ray test", []),