renamed some functions
[eraytracer.git] / raytracer.erl
blob63bc6426a6072156c85173ed214a646e1ecbd672
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 focal_length(Angle, Dimension) ->
34 Dimension/(2*math:tan(Angle*(math:pi()/180)/2)).
36 point_on_screen(_X, _Y, _Camera) ->
37 none.
39 shoot_ray(_From, _Through) ->
40 none.
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).
51 vector_add(V1, V2) ->
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}.
56 vector_sub(V1, V2) ->
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}) ->
62 X*X + Y*Y + Z*Z.
64 vector_mag(V) ->
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,
75 y = A3*B1 - A1*B3,
76 z = A1*B2 - A2*B1}.
78 vector_normalize(V) ->
79 Mag = vector_mag(V),
80 if Mag == 0 ->
81 #vector{x=0, y=0, z=0};
82 true ->
83 vector_scalar_mult(V, 1/vector_mag(V))
84 end.
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
91 scene() ->
92 [#camera{location=#vector{x=0, y=0, z=0},
93 rotation=#vector{x=1, y=0, z=0},
94 fov=90,
95 screen=#screen{width=1, height=1}},
96 #sphere{radius=2,
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
104 {ok, IoDevice} ->
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]),
109 lists:foreach(
110 fun({R, G, B}) ->
111 io:format(IoDevice, "~p ~p ~p ",
112 [R, G, B]) end,
113 Pixels),
114 file:close(IoDevice),
115 io:format("done~n", []);
116 error ->
117 io:format("error opening file~n", [])
118 end.
120 go() ->
121 go(1, 1, "/tmp/traced.ppm").
122 go(Width, Height, Filename) ->
123 write_pixels_to_ppm(Width,
124 Height,
125 255,
126 raytraced_pixel_list(Width,
127 Height,
128 scene()),
129 Filename).
131 % testing
132 scene_test() ->
133 io:format("testing the scene function", []),
134 case scene() of
135 [{camera,
136 {vector, 0, 0, 0},
137 {vector, 1, 0, 0},
139 {screen, 1, 1}},
140 {sphere,
142 {vector, 5, 0, 0},
143 {colour, 0, 128, 255}}] ->
144 true;
145 _Else ->
146 false
147 end.
149 failing_test() ->
150 io:format("this test always fails", []),
151 false.
153 passing_test() ->
154 io:format("this test always passes", []),
155 true.
157 run_tests() ->
158 Tests = [fun scene_test/0,
159 fun passing_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) ->
179 case Success of
180 true ->
181 io:format("Success!~n", []),
183 _Else ->
184 io:format("some tests failed~n", []),
185 failed
186 end;
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
192 true ->
193 io:format(" - OK~n", []);
194 _Else ->
195 io:format(" - FAILED~n", [])
196 end,
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.
260 vector_mag_test() ->
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)),
323 vector_add(
324 vector_cross_product(Vector7, Vector8),
325 vector_cross_product(Vector7, Vector9))),
326 Subtest6 = vectors_equal(Vector1,
327 vector_add(
328 vector_add(
329 vector_cross_product(
330 Vector7,
331 vector_cross_product(Vector8, Vector9)),
332 vector_cross_product(
333 Vector8,
334 vector_cross_product(Vector9, Vector7))),
335 vector_cross_product(
336 Vector9,
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", []),
367 false.
369 ray_shooting_test() ->
370 io:format("ray shooting test", []),
371 false.
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},
377 fov=90,
378 screen=#screen{width=1, height=1}},
379 false.
381 nearest_object_intersecting_ray_test() ->
382 io:format("nearest object intersecting ray test", []),
383 false.
385 focal_length_test() ->
386 Epsilon = 0.1,
387 Size = 36,
388 io:format("focal length test", []),
389 lists:foldl(
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]),
393 Matches
394 and ((Focal_length + Epsilon >= focal_length(
395 Dimension, Size))
396 and (Focal_length - Epsilon =< focal_length(
397 Dimension, Size)))
398 end, true,
399 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).