added more details to the implementation
[eraytracer.git] / raytracer.erl
blob77060eca52b50dd015b65a8e3e4b135a3b1f5874
1 -module(raytracer).
2 -compile(export_all).
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, _) ->
13 done;
14 raytraced_pixel_list(Width, Height, Scene) when Width > 0, Height > 0 ->
15 lists:flatmap(
16 fun(X) ->
17 lists:map(
18 fun(Y) ->
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) ->
31 none.
33 point_on_screen(_X, _Y, _Camera) ->
34 none.
36 shoot_ray(_From, _Through) ->
37 none.
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}) ->
59 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,
72 y = A3*B1 - A1*B3,
73 z = A1*B2 - A2*B1}.
75 vector_normalize(V) ->
76 Mag = vector_magnitude(V),
77 if Mag == 0 ->
78 #vector{x=0, y=0, z=0};
79 true ->
80 vector_scalar_mult(V, 1/vector_magnitude(V))
81 end.
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
88 scene() ->
89 [#camera{location=#vector{x=0, y=0, z=0},
90 rotation=#vector{x=1, y=0, z=0},
91 fov=90,
92 screen=#screen{width=1, height=1}},
93 #sphere{radius=2,
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
101 {ok, IoDevice} ->
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]),
106 lists:foreach(
107 fun({R, G, B}) ->
108 io:format(IoDevice, "~p ~p ~p ",
109 [R, G, B]) end,
110 Pixels),
111 file:close(IoDevice),
112 io:format("done~n", []);
113 error ->
114 io:format("error opening file~n", [])
115 end.
117 go() ->
118 go(1, 1, "/tmp/traced.ppm").
119 go(Width, Height, Filename) ->
120 write_pixels_to_ppm(Width,
121 Height,
122 255,
123 raytraced_pixel_list(Width,
124 Height,
125 scene()),
126 Filename).
128 % testing
129 scene_test() ->
130 io:format("testing the scene function", []),
131 case scene() of
132 [{camera,
133 {vector, 0, 0, 0},
134 {vector, 1, 0, 0},
136 {screen, 1, 1}},
137 {sphere,
139 {vector, 5, 0, 0},
140 {colour, 0, 128, 255}}] ->
141 true;
142 _Else ->
143 false
144 end.
146 failing_test() ->
147 io:format("this test always fails", []),
148 false.
150 passing_test() ->
151 io:format("this test always passes", []),
152 true.
154 run_tests() ->
155 Tests = [fun scene_test/0,
156 fun passing_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) ->
175 case Success of
176 true ->
177 io:format("Success!~n", []),
179 _Else ->
180 io:format("some tests failed~n", []),
181 failed
182 end;
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
188 true ->
189 io:format(" - OK~n", []);
190 _Else ->
191 io:format(" - FAILED~n", [])
192 end,
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)),
319 add_vectors(
320 vector_cross_product(Vector7, Vector8),
321 vector_cross_product(Vector7, Vector9))),
322 Subtest6 = vectors_equal(Vector1,
323 add_vectors(
324 add_vectors(
325 vector_cross_product(
326 Vector7,
327 vector_cross_product(Vector8, Vector9)),
328 vector_cross_product(
329 Vector8,
330 vector_cross_product(Vector9, Vector7))),
331 vector_cross_product(
332 Vector9,
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", []),
363 false.
365 ray_shooting_test() ->
366 io:format("ray shooting test", []),
367 false.
369 point_on_screen_test() ->
370 io:format("point on screen test", []),
371 false.
373 nearest_object_intersecting_ray_test() ->
374 io:format("nearest object intersecting ray test", []),
375 false.