4 %% a simple raytracer written in Erlang
6 %% Copyright (c) 2008 Michael Ploujnikov
8 %% This program is free software: you can redistribute it and/or modify
9 %% it under the terms of the GNU General Public License as published by
10 %% the Free Software Foundation, either version 2 of the License, or
11 %% (at your option) any later version.
13 %% This program is distributed in the hope that it will be useful,
14 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
15 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 %% GNU General Public License for more details.
18 %% You should have received a copy of the GNU General Public License
19 %% along with this program. If not, see <http://www.gnu.org/licenses/>.
22 %% * three object types:
25 %% * triangles (not done)
28 %% * lighting based on local illumination models
29 %% * ambient (not done)
32 %% * attenuation (not done)
33 %% * reflections to a fixed depth
34 %% * PPM output file format
35 %% * randomly generated scene (not done)
36 %% * useful test suite (working but not very friendly when fails)
38 %% * specify how many pixels to give to each process
39 %% * distributed (across multiple computers)
42 %% The simplest way to run this raytracer is through an Erlang shell with the go() function.
43 %% At the very least, the go function expects one parameter,
44 %% which tells it how to run the raytracer:
45 %% To execute in a simple, serial manner use:
46 %% > raytracer:go(simple)
47 %% To execute in a parallel manner (taking advantage of multiple CPUs available to the system) use:
48 %% > raytracer:go(concurrent)
49 %% To execute in a distributed manner use:
50 %% > raytracer:go(distributed)
51 %% For the last command to work you need to do some preparation of the nodes (computers) that will be running the raytracer. To same myself some time I'll assume that you know how to do that or can figure out how to do that by reading http://erlang.org/doc/getting_started/part_frame.html and then http://www.erlang.org/doc/man/pool.html .
52 %% Read the rest of the code for additional parameters that the go() function takes
53 %% See the example *.sh files for examples of standalone invocation
67 raytraced_pixel_list_simple
/4,
68 raytraced_pixel_list_concurrent
/4,
69 raytraced_pixel_list_distributed
/4
72 -record(vector
, {x
, y
, z
}).
73 -record(colour
, {r
, g
, b
}).
74 -record(ray
, {origin
, direction
}).
75 -record(screen
, {width
, height
}). % screen dimensions in the 3D world
76 -record(camera
, {location
, rotation
, fov
, screen
}).
77 -record(material
, {colour
, specular_power
, shininess
, reflectivity
}).
78 -record(sphere
, {radius
, center
, material
}).
79 -record(triangle
, {v1
, v2
, v3
, material
}).
80 -record(plane
, {normal
, distance
, material
}).
81 -record(point_light
, {diffuse_colour
, location
, specular_colour
}).
82 -define(BACKGROUND_COLOUR
, #colour
{r
=0, g
=0, b
=0}).
83 -define(UNKNOWN_COLOUR
, #colour
{r
=0, g
=1, b
=0}).
84 -define(FOG_DISTANCE
, 40).
86 raytraced_pixel_list_simple(0, 0, _
, _
) ->
88 raytraced_pixel_list_simple(Width
, Height
, Scene
, Recursion_depth
)
89 when Width
> 0, Height
> 0 ->
94 % coordinates passed as a percentage
96 trace_ray_through_pixel(
97 {X
/Width
, Y
/Height
}, Scene
, Recursion_depth
))} end,
98 lists:seq(0, Width
- 1)) end,
99 lists:seq(0, Height
- 1)).
101 raytraced_pixel_list_concurrent(0, 0, _
, _
) ->
103 raytraced_pixel_list_concurrent(Width
, Height
, Scene
, Recursion_depth
)
104 when Width
> 0, Height
> 0 ->
105 Master_PID
= spawn(raytracer
, master
, [self(), Width
*Height
]),
110 % coordinates passed as a percentage
111 spawn(raytracer
, worker
,
112 [Master_PID
, X
+Y
*Width
, {X
/Width
, Y
/Height
}, Scene
, Recursion_depth
]) end,
113 lists:seq(0, Width
- 1)) end,
114 lists:seq(0, Height
- 1)),
115 io:format("all workers have been spawned~n", []),
121 raytraced_pixel_list_distributed(0, 0, _
, _
) ->
123 raytraced_pixel_list_distributed(Width
, Height
, Scene
, Recursion_depth
)
124 when Width
> 0, Height
> 0 ->
125 io:format("distributed tracing~n", []),
126 Pool_master
= pool:start(renderslave
),
127 io:format("Pool master is ~p~n", [Pool_master
]),
128 io:format("Nodes are ~p~n", [pool:get_nodes()]),
129 Master_PID
= pool:pspawn(raytracer
, master
, [self(), Width
*Height
]),
130 Pixels
= [{X
, Y
} || X
<- lists:seq(0, Width
-1), Y
<- lists:seq(0, Height
-1)],
131 distribute_work(Pixels
, trunc(Width
*Height
/64), Master_PID
, Width
, Height
, Scene
,
133 io:format("all workers have been spawned~n", []),
139 distribute_work(Pixels
, Pixels_per_worker
, Master_PID
, Width
, Height
, Scene
,
140 Recursion_depth
) when length(Pixels
) > Pixels_per_worker
->
141 {To_work_on
, The_rest
} = lists:split(Pixels_per_worker
, Pixels
),
142 pool:pspawn(raytracer
, distributed_worker
,
143 [Master_PID
, To_work_on
, Width
, Height
, Scene
, Recursion_depth
]),
144 distribute_work(The_rest
, Pixels_per_worker
, Master_PID
,
145 Width
, Height
, Scene
, Recursion_depth
);
146 distribute_work(Pixels
, _Pixels_per_worker
, Master_PID
, Width
, Height
, Scene
,
148 pool:pspawn(raytracer
, distributed_worker
,
149 [Master_PID
, Pixels
, Width
, Height
, Scene
, Recursion_depth
]).
151 master(Program_PID
, Pixel_count
) ->
152 master(Program_PID
, Pixel_count
, []).
153 master(Program_PID
, 0, Pixel_list
) ->
154 io:format("master is done~n", []),
155 Program_PID
! lists:keysort(1, Pixel_list
);
156 % assumes all workers eventually return a good value
157 master(Program_PID
, Pixel_count
, Pixel_list
) ->
160 master(Program_PID
, Pixel_count
-1, [Pixel_tuple
|Pixel_list
])
164 % assumes X and Y are percentages of the screen dimensions
165 worker(Master_PID
, Pixel_num
, {X
, Y
}, Scene
, Recursion_depth
) ->
166 Master_PID
! {Pixel_num
,
167 colour_to_pixel(trace_ray_through_pixel({X
, Y
}, Scene
, Recursion_depth
))}.
169 distributed_worker(Master_PID
, Pixels
, Width
, Height
, Scene
, Recursion_depth
) ->
170 %io:format("~pworker doing ~p pixels=~p~n", [node(), length(Pixels), Pixels]),
173 Master_PID
! {X
+Y
*Width
,
175 trace_ray_through_pixel(
176 {X
/Width
, Y
/Height
}, Scene
, Recursion_depth
))}
180 trace_ray_through_pixel({X
, Y
}, [Camera
|Rest_of_scene
], Recursion_depth
) ->
181 pixel_colour_from_ray(
182 ray_through_pixel(X
, Y
, Camera
),
186 pixel_colour_from_ray(_Ray
, _Scene
, 0) ->
187 #colour
{r
=0, g
=0, b
=0};
188 pixel_colour_from_ray(Ray
, Scene
, Recursion_depth
) ->
189 case nearest_object_intersecting_ray(Ray
, Scene
) of
190 {Nearest_object
, _Distance
, Hit_location
, Hit_normal
} ->
191 %io:format("hit: ~w~n", [{Nearest_object, _Distance}]),
193 vector_to_colour(lighting_function(Ray
,
203 % my own illumination formula
204 % ideas were borrowed from:
205 % http://www.devmaster.net/wiki/Lighting
206 % http://svn.icculus.org/darkwar/trunk/base/shaders/light.frag?rev=1067&view=auto
207 lighting_function(Ray
, Object
, Hit_location
, Hit_normal
, Scene
,
210 fun (#point_light
{diffuse_colour
=Light_colour
,
211 location
=Light_location
,
212 specular_colour
=Specular_colour
},
214 Reflection
= vector_scalar_mult(
216 pixel_colour_from_ray(
217 #ray
{origin
=Hit_location
,
218 direction
=vector_bounce_off_plane(
219 Ray#ray
.direction
, Hit_normal
)},
222 object_reflectivity(Object
)),
223 Light_contribution
= vector_add(
234 object_specular_power(Object
),
235 object_shininess(Object
),
242 vector_component_mult(
243 colour_to_vector(Light_colour
),
245 shadow_factor(Light_location
, Hit_location
, Scene
))));
246 (_Not_a_point_light
, Final_colour
) ->
249 #vector
{x
=0, y
=0, z
=0},
252 % returns 0 when the Hit_location is completely occluded from light
254 % otherwise returns 1
255 shadow_factor(Light_location
, Hit_location
, Scene
) ->
256 Light_vector
= vector_sub(Light_location
, Hit_location
),
257 Light_vector_length
= vector_mag(Light_vector
),
258 Light_direction
= vector_normalize(Light_vector
),
259 % start the ray a little bit farther to prevent artefacts due to unit precision limitations
260 Shadow_ray
= #ray
{origin
=vector_add(
265 direction
=Light_direction
},
266 case nearest_object_intersecting_ray(Shadow_ray
, Scene
) of
267 {_Obj
, Distance
, _Loc
, _Normal
} ->
268 if Distance
== infinity
->
270 Light_vector_length
> Distance
->
280 % http://www.devmaster.net/wiki/Lambert_diffuse_lighting
281 % http://svn.icculus.org/darkwar/trunk/base/shaders/light.frag?rev=1067&view=auto
282 diffuse_term(Object
, Light_location
, Hit_location
, Hit_normal
) ->
284 colour_to_vector(object_diffuse_colour(Object
)),
286 vector_dot_product(Hit_normal
,
288 vector_sub(Light_location
,
292 % http://svn.icculus.org/darkwar/trunk/base/shaders/light.frag?rev=1067&view=auto
293 % http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_2_Phong_Mirrors_and_Shadows.shtml
294 % http://www.devmaster.net/wiki/Phong_shading
295 specular_term(EyeVector
, Light_location
, Hit_location
, Hit_normal
,
296 Specular_power
, Shininess
, Specular_colour
) ->
298 colour_to_vector(Specular_colour
),
305 vector_sub(Light_location
, Hit_location
)),
306 vector_neg(EyeVector
))),
307 Hit_normal
)]), Specular_power
)).
309 % object agnostic intersection function
310 nearest_object_intersecting_ray(Ray
, Scene
) ->
311 nearest_object_intersecting_ray(
312 Ray
, none
, hitlocation
, hitnormal
, infinity
, Scene
).
313 nearest_object_intersecting_ray(
314 _Ray
, _NearestObj
, _Hit_location
, _Normal
, infinity
, []) ->
316 nearest_object_intersecting_ray(
317 _Ray
, NearestObj
, Hit_location
, Normal
, Distance
, []) ->
318 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
319 {NearestObj
, Distance
, Hit_location
, Normal
};
320 nearest_object_intersecting_ray(Ray
,
325 [CurrentObject
|Rest_of_scene
]) ->
326 case ray_object_intersect(Ray
, CurrentObject
) of
327 {NewDistance
, New_hit_location
, New_normal
} ->
328 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
329 if (Distance
== infinity
) or (Distance
> NewDistance
) ->
330 %io:format("another closer object found~n", []),
331 nearest_object_intersecting_ray(
339 %io:format("no closer obj found~n", []),
340 nearest_object_intersecting_ray(
349 nearest_object_intersecting_ray(
358 % object specific intersection function
359 ray_object_intersect(Ray
, Object
) ->
362 ray_sphere_intersect(Ray
, Object
);
364 ray_triangle_intersect(Ray
, Object
);
366 ray_plane_intersect(Ray
, Object
);
372 % http://www.devmaster.net/articles/raytracing/
373 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter1.htm
374 ray_sphere_intersect(
379 #sphere
{radius
=Radius
, center
=#vector
{
380 x
=Xc
, y
=Yc
, z
=Zc
}}) ->
382 A
= Xd
*Xd
+ Yd
*Yd
+ Zd
*Zd
,
383 B
= 2 * (Xd
*(X0
-Xc
) + Yd
*(Y0
-Yc
) + Zd
*(Z0
-Zc
)),
384 C
= (X0
-Xc
)*(X0
-Xc
) + (Y0
-Yc
)*(Y0
-Yc
) + (Z0
-Zc
)*(Z0
-Zc
) - Radius
*Radius
,
385 Discriminant
= B
*B
- 4*A
*C
,
386 %io:format("A=~w B=~w C=~w discriminant=~w~n",
387 % [A, B, C, Discriminant]),
388 if Discriminant
>= Epsilon
->
389 T0
= (-B
+ math:sqrt(Discriminant
))/2,
390 T1
= (-B
- math:sqrt(Discriminant
))/2,
391 if (T0
>= 0) and (T1
>= 0) ->
392 %io:format("T0=~w T1=~w~n", [T0, T1]),
393 Distance
= lists:min([T0
, T1
]),
394 Intersection
= vector_add(
395 #vector
{x
=X0
, y
=Y0
, z
=Z0
},
397 #vector
{x
=Xd
, y
=Yd
, z
=Zd
}, Distance
)),
398 Normal
= vector_normalize(
399 vector_sub(Intersection
,
400 #vector
{x
=Xc
, y
=Yc
, z
=Zc
})),
401 {Distance
, Intersection
, Normal
};
410 % http://www.graphics.cornell.edu/pubs/1997/MT97.html
411 % http://jgt.akpeters.com/papers/GuigueDevillers03/addendum.html
412 ray_triangle_intersect(Ray
, Triangle
) ->
415 % find vectors for two edges sharing v1
416 Edge1
= vector_sub(Triangle#triangle
.v2
, Triangle#triangle
.v1
),
417 Edge2
= vector_sub(Triangle#triangle
.v3
, Triangle#triangle
.v1
),
419 % begin calculating determinant
420 P
= vector_cross_product(Ray#ray
.direction
, Edge2
),
421 Determinant
= vector_dot_product(Edge1
, P
),
423 % negative determinant means the triangle is facing away
426 if Determinant
< Epsilon
->
427 % for our purposes we ignore such triangles
428 %% io:format("ray is either behind or on the triangle: ~p~n", [Determinant]),
431 % calculate the distance from v1 to ray origin
432 T
= vector_sub(Ray#ray
.origin
, Triangle#triangle
.v1
),
434 % calculate the U parameter and test bounds
435 U
= vector_dot_product(T
, P
),
436 if (U
< 0) or (U
> Determinant
) ->
437 %% io:format("U is negative or greater than det: ~p~n", [U]),
440 % prepare to test the V parameter
441 Q
= vector_cross_product(T
, Edge1
),
442 % calculate the V parameter and test bounds
443 V
= vector_dot_product(Ray#ray
.direction
, Q
),
444 if (V
< 0) or (U
+V
> Determinant
) ->
445 %% io:format("V less than 0.0 or U+V greater than det: ~p ~p~n",
449 % calculate the distance to the
450 % intersection point and return
451 %% io:format("found ray/triangle intersection ~n", []),
452 Distance
= vector_dot_product(Edge2
, Q
) / Determinant
,
453 Intersection
= vector_add(
458 Normal
= vector_normalize(
460 Triangle#triangle
.v1
,
461 Triangle#triangle
.v2
)),
462 {Distance
, Intersection
, Normal
}
468 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
469 % http://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm
470 % http://www.devmaster.net/articles/raytracing/
471 ray_plane_intersect(Ray
, Plane
) ->
473 Vd
= vector_dot_product(Plane#plane
.normal
, Ray#ray
.direction
),
475 V0
= -(vector_dot_product(Plane#plane
.normal
, Ray#ray
.origin
)
476 + Plane#plane
.distance
),
478 if Distance
< Epsilon
->
481 Intersection
= vector_add(
486 {Distance
, Intersection
, Plane#plane
.normal
}
493 focal_length(Angle
, Dimension
) ->
494 Dimension
/(2*math:tan(Angle
*(math:pi()/180)/2)).
496 point_on_screen(X
, Y
, Camera
) ->
497 %TODO: implement rotation (using quaternions)
498 Screen_width
= (Camera#camera
.screen
)#screen
.width
,
499 Screen_height
= (Camera#camera
.screen
)#screen
.height
,
500 lists:foldl(fun(Vect
, Sum
) -> vector_add(Vect
, Sum
) end,
501 Camera#camera
.location
,
503 #vector
{x
=0, y
=0, z
=1},
507 #vector
{x
= (X
-0.5) * Screen_width
,
511 y
= (Y
-0.5) * Screen_height
,
516 shoot_ray(From
, Through
) ->
517 #ray
{origin
=From
, direction
=vector_normalize(vector_sub(Through
, From
))}.
519 % assume that X and Y are percentages of the 3D world screen dimensions
520 ray_through_pixel(X
, Y
, Camera
) ->
521 shoot_ray(Camera#camera
.location
, point_on_screen(X
, Y
, Camera
)).
523 vectors_equal(V1
, V2
) ->
524 vectors_equal(V1
, V2
, 0.0001).
525 vectors_equal(V1
, V2
, Epsilon
) ->
526 (V1#vector
.x
+ Epsilon
>= V2#vector
.x
)
527 and (V1#vector
.x
- Epsilon
=<V2#vector
.x
)
528 and (V1#vector
.y
+ Epsilon
>= V2#vector
.y
)
529 and (V1#vector
.y
- Epsilon
=<V2#vector
.y
)
530 and (V1#vector
.z
+ Epsilon
>= V2#vector
.z
)
531 and (V1#vector
.z
- Epsilon
=<V2#vector
.z
).
534 vector_add(V1
, V2
) ->
535 #vector
{x
= V1#vector
.x
+ V2#vector
.x
,
536 y
= V1#vector
.y
+ V2#vector
.y
,
537 z
= V1#vector
.z
+ V2#vector
.z
}.
539 vector_sub(V1
, V2
) ->
540 #vector
{x
= V1#vector
.x
- V2#vector
.x
,
541 y
= V1#vector
.y
- V2#vector
.y
,
542 z
= V1#vector
.z
- V2#vector
.z
}.
544 vector_square_mag(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
548 math:sqrt(vector_square_mag(V
)).
550 vector_scalar_mult(#vector
{x
=X
, y
=Y
, z
=Z
}, Scalar
) ->
551 #vector
{x
=X
*Scalar
, y
=Y
*Scalar
, z
=Z
*Scalar
}.
553 vector_component_mult(#vector
{x
=X1
, y
=Y1
, z
=Z1
}, #vector
{x
=X2
, y
=Y2
, z
=Z2
}) ->
554 #vector
{x
=X1
*X2
, y
=Y1
*Y2
, z
=Z1
*Z2
}.
556 vector_dot_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
557 A1
*B1
+ A2
*B2
+ A3
*B3
.
559 vector_cross_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
560 #vector
{x
= A2
*B3
- A3
*B2
,
564 vector_normalize(V
) ->
567 #vector
{x
=0, y
=0, z
=0};
569 vector_scalar_mult(V
, 1/vector_mag(V
))
572 vector_neg(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
573 #vector
{x
=-X
, y
=-Y
, z
=-Z
}.
576 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtreflec.htm
577 % http://www.devmaster.net/articles/raytracing/
578 vector_bounce_off_plane(Vector
, Normal
) ->
582 2*vector_dot_product(Normal
, vector_neg(Vector
))),
585 object_diffuse_colour(#sphere
{material
=#material
{colour
=C
}}) ->
587 object_diffuse_colour(#plane
{material
=#material
{colour
=C
}}) ->
589 object_diffuse_colour(#triangle
{material
=#material
{colour
=C
}}) ->
592 object_specular_power(#sphere
{material
=#material
{specular_power
=SP
}}) ->
594 object_specular_power(#plane
{material
=#material
{specular_power
=SP
}}) ->
596 object_specular_power(#triangle
{material
=#material
{specular_power
=SP
}}) ->
599 object_shininess(#sphere
{material
=#material
{shininess
=S
}}) ->
601 object_shininess(#plane
{material
=#material
{shininess
=S
}}) ->
603 object_shininess(#triangle
{material
=#material
{shininess
=S
}}) ->
606 object_reflectivity(#sphere
{material
=#material
{reflectivity
=R
}}) ->
608 object_reflectivity(#plane
{material
=#material
{reflectivity
=R
}}) ->
610 object_reflectivity(#triangle
{material
=#material
{reflectivity
=R
}}) ->
613 point_on_sphere(#sphere
{radius
=Radius
, center
=#vector
{x
=XC
, y
=YC
, z
=ZC
}},
614 #vector
{x
=X
, y
=Y
, z
=Z
}) ->
617 ((X
-XC
)*(X
-XC
) + (Y
-YC
)*(Y
-YC
) + (Z
-ZC
)*(Z
-ZC
)) - Radius
*Radius
).
619 colour_to_vector(#colour
{r
=R
, g
=G
, b
=B
}) ->
620 #vector
{x
=R
, y
=G
, z
=B
}.
621 vector_to_colour(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
622 #colour
{r
=X
, g
=Y
, b
=Z
}.
623 colour_to_pixel(#colour
{r
=R
, g
=G
, b
=B
}) ->
626 % returns a list of objects in the scene
627 % camera is assumed to be the first element in the scene
629 [#camera
{location
=#vector
{x
=0, y
=0, z
=-2},
630 rotation
=#vector
{x
=0, y
=0, z
=0},
632 screen
=#screen
{width
=4, height
=3}},
633 #point_light
{diffuse_colour
=#colour
{r
=1, g
=1, b
=0.5},
634 location
=#vector
{x
=5, y
=-2, z
=0},
635 specular_colour
=#colour
{r
=1, g
=1, b
=1}},
636 #point_light
{diffuse_colour
=#colour
{r
=1, g
=0, b
=0.5},
637 location
=#vector
{x
=-10, y
=0, z
=7},
638 specular_colour
=#colour
{r
=1, g
=0, b
=0.5}},
640 center
=#vector
{x
=4, y
=0, z
=10},
642 colour
=#colour
{r
=0, g
=0.5, b
=1},
647 center
=#vector
{x
=-5, y
=3, z
=9},
649 colour
=#colour
{r
=1, g
=0.5, b
=0},
654 center
=#vector
{x
=-4.5, y
=-2.5, z
=14},
656 colour
=#colour
{r
=0.5, g
=1, b
=0},
660 #triangle
{v1
=#vector
{x
=-2, y
=5, z
=5},
661 v2
=#vector
{x
=4, y
=5, z
=10},
662 v3
=#vector
{x
=4, y
=-5, z
=10},
664 colour
=#colour
{r
=1, g
=0.5, b
=0},
668 #plane
{normal
=#vector
{x
=0, y
=-1, z
=0},
671 colour
=#colour
{r
=1, g
=1, b
=1},
677 % assumes Pixels are ordered in a row by row fasion
678 write_pixels_to_ppm(Width
, Height
, MaxValue
, Pixels
, Filename
) ->
679 case file:open(Filename
, write
) of
681 io:format("file opened~n", []),
682 io:format(IoDevice
, "P3~n", []),
683 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
684 io:format(IoDevice
, "~p~n", [MaxValue
]),
686 fun({_Num
, {R
, G
, B
}}) ->
687 io:format(IoDevice
, "~p ~p ~p ",
688 [lists:min([trunc(R
*MaxValue
), MaxValue
]),
689 lists:min([trunc(G
*MaxValue
), MaxValue
]),
690 lists:min([trunc(B
*MaxValue
), MaxValue
])]) end,
692 file:close(IoDevice
);
694 io:format("error opening file~n", [])
697 % various invocation style functions
698 standalone([Width
, Height
, Filename
, Recursion_depth
, Strategy
]) ->
699 standalone(list_to_integer(Width
),
700 list_to_integer(Height
),
702 list_to_integer(Recursion_depth
),
703 tracing_function(list_to_atom(Strategy
))).
705 standalone(Width
, Height
, Filename
, Recursion_depth
, Function
) ->
706 {Time
, _Value
} = timer:tc(
714 io:format("Done in ~w seconds~n", [Time
/1000000]),
718 raytrace(tracing_function(Strategy
)).
720 go(Width
, Height
, Filename
, Recursion_depth
, Strategy
) ->
721 raytrace(Width
, Height
, Filename
, Recursion_depth
,
722 tracing_function(Strategy
)).
724 tracing_function(simple
) ->
725 fun raytraced_pixel_list_simple
/4;
726 tracing_function(concurrent
) ->
727 fun raytraced_pixel_list_concurrent
/4;
728 tracing_function(distributed
) ->
729 fun raytraced_pixel_list_distributed
/4.
731 raytrace(Function
) ->
732 raytrace(4, 3, "/tmp/traced.ppm", 5, Function
).
733 raytrace(Width
, Height
, Filename
, Recursion_depth
, Function
) ->
747 Tests
= [fun scene_test
/0,
749 fun vector_equality_test
/0,
750 fun vector_addition_test
/0,
751 fun vector_subtraction_test
/0,
752 fun vector_square_mag_test
/0,
753 fun vector_mag_test
/0,
754 fun vector_scalar_multiplication_test
/0,
755 fun vector_dot_product_test
/0,
756 fun vector_cross_product_test
/0,
757 fun vector_normalization_test
/0,
758 fun vector_negation_test
/0,
759 % fun ray_through_pixel_test/0,
760 fun ray_shooting_test
/0,
761 fun point_on_screen_test
/0,
762 fun nearest_object_intersecting_ray_test
/0,
763 fun focal_length_test
/0,
764 % fun vector_rotation_test/0,
765 fun vector_bounce_off_plane_test
/0,
766 fun ray_sphere_intersection_test
/0
768 run_tests(Tests
, 1, true
).
771 io:format("testing the scene function", []),
785 {colour
, 1, 0, 0.5}},
789 {material
, {colour
, 0, 0.5, 1}, 20, 1, 0.1}},
793 {material
, {colour
, 1, 0.5, 0}, 4, 0.25, 0.5}},
796 {vector
, -4.5, -2.5, 14},
797 {material
, {colour
, 0.5, 1, 0}, 20, 0.25, 0.7}},
802 {material
, {colour
, 1, 0.5, 0}, 4, 0.25, 0.5}},
806 {material
, {colour
, 1, 1, 1}, 1, 0, 0.01}}
814 io:format("this test always passes", []),
817 run_tests([], _Num
, Success
) ->
820 io:format("Success!~n", []),
823 io:format("some tests failed~n", []),
827 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
828 io:format("test #~p: ", [Num
]),
829 Current_success
= First_test(),
830 case Current_success
of
832 io:format(" - OK~n", []);
834 io:format(" - FAILED~n", [])
836 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
838 vector_equality_test() ->
839 io:format("vector equality"),
840 Vector1
= #vector
{x
=0, y
=0, z
=0},
841 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
842 Vector3
= #vector
{x
=0.0983, y
=0.0214, z
=0.12342},
843 Vector4
= #vector
{x
=0.0984, y
=0.0213, z
=0.12341},
844 Vector5
= #vector
{x
=10/3, y
=-10/6, z
=8/7},
845 Vector6
= #vector
{x
=3.3, y
=-1.6, z
=1.1},
847 Subtest1
= vectors_equal(Vector1
, Vector1
)
848 and
vectors_equal(Vector2
, Vector2
)
849 and
not (vectors_equal(Vector1
, Vector2
))
850 and
not (vectors_equal(Vector2
, Vector1
)),
851 Subtest2
= vectors_equal(Vector3
, Vector4
, 0.0001),
852 Subtest3
= vectors_equal(Vector5
, Vector6
, 0.1),
854 Subtest1 and Subtest2 and Subtest3
.
857 vector_addition_test() ->
858 io:format("vector addition", []),
859 Vector0
= vector_add(
860 #vector
{x
=3, y
=7, z
=-3},
861 #vector
{x
=0, y
=-24, z
=123}),
862 Subtest1
= (Vector0#vector
.x
== 3)
863 and (Vector0#vector
.y
== -17)
864 and (Vector0#vector
.z
== 120),
866 Vector1
= #vector
{x
=5, y
=0, z
=984},
867 Vector2
= vector_add(Vector1
, Vector1
),
868 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
869 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
870 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
872 Vector3
= #vector
{x
=908, y
=-098, z
=234},
873 Vector4
= vector_add(Vector3
, #vector
{x
=0, y
=0, z
=0}),
874 Subtest3
= vectors_equal(Vector3
, Vector4
),
876 Subtest1 and Subtest2 and Subtest3
.
878 vector_subtraction_test() ->
879 io:format("vector subtraction", []),
880 Vector1
= #vector
{x
=0, y
=0, z
=0},
881 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
882 Vector3
= #vector
{x
=1, y
=1, z
=1},
883 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
885 Subtest1
= vectors_equal(Vector1
, vector_sub(Vector1
, Vector1
)),
886 Subtest2
= vectors_equal(Vector3
, vector_sub(Vector3
, Vector1
)),
887 Subtest3
= not
vectors_equal(Vector3
, vector_sub(Vector1
, Vector3
)),
888 Subtest4
= vectors_equal(Vector4
, vector_sub(Vector4
, Vector1
)),
889 Subtest5
= not
vectors_equal(Vector4
, vector_sub(Vector1
, Vector4
)),
890 Subtest5
= vectors_equal(vector_add(Vector2
, Vector4
),
891 vector_sub(Vector2
, Vector3
)),
893 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
895 vector_square_mag_test() ->
896 io:format("vector square magnitude test", []),
897 Vector1
= #vector
{x
=0, y
=0, z
=0},
898 Vector2
= #vector
{x
=1, y
=1, z
=1},
899 Vector3
= #vector
{x
=3, y
=-4, z
=0},
901 Subtest1
= (0 == vector_square_mag(Vector1
)),
902 Subtest2
= (3 == vector_square_mag(Vector2
)),
903 Subtest3
= (25 == vector_square_mag(Vector3
)),
905 Subtest1 and Subtest2 and Subtest3
.
908 io:format("vector magnitude test", []),
909 Vector1
= #vector
{x
=0, y
=0, z
=0},
910 Vector2
= #vector
{x
=1, y
=1, z
=1},
911 Vector3
= #vector
{x
=3, y
=-4, z
=0},
913 Subtest1
= (0 == vector_mag(Vector1
)),
914 Subtest2
= (math:sqrt(3) == vector_mag(Vector2
)),
915 Subtest3
= (5 == vector_mag(Vector3
)),
917 Subtest1 and Subtest2 and Subtest3
.
919 vector_scalar_multiplication_test() ->
920 io:format("scalar multiplication test", []),
921 Vector1
= #vector
{x
=0, y
=0, z
=0},
922 Vector2
= #vector
{x
=1, y
=1, z
=1},
923 Vector3
= #vector
{x
=3, y
=-4, z
=0},
925 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
926 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
927 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
928 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
929 vector_scalar_mult(Vector2
, 4)),
930 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
931 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
933 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
935 vector_dot_product_test() ->
936 io:format("dot product test", []),
937 Vector1
= #vector
{x
=1, y
=3, z
=-5},
938 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
939 Vector3
= #vector
{x
=0, y
=0, z
=0},
940 Vector4
= #vector
{x
=1, y
=0, z
=0},
941 Vector5
= #vector
{x
=0, y
=1, z
=0},
943 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
944 Subtest2
= vector_dot_product(Vector2
, Vector2
)
945 == vector_square_mag(Vector2
),
946 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
947 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
949 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
951 vector_cross_product_test() ->
952 io:format("cross product test", []),
953 Vector1
= #vector
{x
=0, y
=0, z
=0},
954 Vector2
= #vector
{x
=1, y
=0, z
=0},
955 Vector3
= #vector
{x
=0, y
=1, z
=0},
956 Vector4
= #vector
{x
=0, y
=0, z
=1},
957 Vector5
= #vector
{x
=1, y
=2, z
=3},
958 Vector6
= #vector
{x
=4, y
=5, z
=6},
959 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
960 Vector8
= #vector
{x
=-1, y
=0, z
=0},
961 Vector9
= #vector
{x
=-9, y
=8, z
=433},
963 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
964 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
965 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
966 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
967 Subtest5
= vectors_equal(
968 vector_cross_product(Vector7
,
969 vector_add(Vector8
, Vector9
)),
971 vector_cross_product(Vector7
, Vector8
),
972 vector_cross_product(Vector7
, Vector9
))),
973 Subtest6
= vectors_equal(Vector1
,
976 vector_cross_product(
978 vector_cross_product(Vector8
, Vector9
)),
979 vector_cross_product(
981 vector_cross_product(Vector9
, Vector7
))),
982 vector_cross_product(
984 vector_cross_product(Vector7
, Vector8
)))),
986 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
988 vector_normalization_test() ->
989 io:format("normalization test", []),
990 Vector1
= #vector
{x
=0, y
=0, z
=0},
991 Vector2
= #vector
{x
=1, y
=0, z
=0},
992 Vector3
= #vector
{x
=5, y
=0, z
=0},
994 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
995 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
996 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
997 Subtest4
= vectors_equal(Vector2
, vector_normalize(
998 vector_scalar_mult(Vector2
, 324))),
1000 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
1002 vector_negation_test() ->
1003 io:format("vector negation test", []),
1004 Vector1
= #vector
{x
=0, y
=0, z
=0},
1005 Vector2
= #vector
{x
=4, y
=-5, z
=6},
1007 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
1008 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
1010 Subtest1 and Subtest2
.
1012 ray_shooting_test() ->
1013 io:format("ray shooting test"),
1014 Vector1
= #vector
{x
=0, y
=0, z
=0},
1015 Vector2
= #vector
{x
=1, y
=0, z
=0},
1017 Subtest1
= vectors_equal(
1018 (shoot_ray(Vector1
, Vector2
))#ray
.direction
,
1023 ray_sphere_intersection_test() ->
1024 io:format("ray sphere intersection test", []),
1028 center
=#vector
{x
= 0, y
=0, z
=10},
1030 colour
=#colour
{r
=0.4, g
=0.4, b
=0.4}}},
1032 origin
=#vector
{x
=0, y
=0, z
=0},
1033 direction
=#vector
{x
=0, y
=0, z
=1}},
1035 origin
=#vector
{x
=3, y
=0, z
=0},
1036 direction
=#vector
{x
=0, y
=0, z
=1}},
1038 origin
=#vector
{x
=4, y
=0, z
=0},
1039 direction
=#vector
{x
=0, y
=0, z
=1}},
1040 {Distance1
, _Hit_location1
, _Hit_normal1
} = ray_sphere_intersect(Ray1
, Sphere
),
1041 Subtest1
= Distance1
== 7.0,
1042 Subtest2
= ray_sphere_intersect(Ray2
, Sphere
) == none
,
1043 Subtest3
= ray_sphere_intersect(Ray3
, Sphere
) == none
,
1044 Subtest1 and Subtest2 and Subtest3
.
1046 point_on_screen_test() ->
1047 io:format("point on screen test", []),
1048 Camera1
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
1049 rotation
=#vector
{x
=0, y
=0, z
=0},
1051 screen
=#screen
{width
=1, height
=1}},
1052 Camera2
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
1053 rotation
=#vector
{x
=0, y
=0, z
=0},
1055 screen
=#screen
{width
=640, height
=480}},
1057 Subtest1
= vectors_equal(
1058 #vector
{x
=0, y
=0, z
=0.5},
1059 point_on_screen(0.5, 0.5, Camera1
)),
1060 Subtest2
= vectors_equal(
1061 #vector
{x
=-0.5, y
=-0.5, z
=0.5},
1062 point_on_screen(0, 0, Camera1
)),
1063 Subtest3
= vectors_equal(
1064 #vector
{x
=0.5, y
=0.5, z
=0.5},
1065 point_on_screen(1, 1, Camera1
)),
1066 Subtest4
= vectors_equal(
1067 point_on_screen(0, 0, Camera2
),
1068 #vector
{x
=-320, y
=-240, z
=320}),
1069 Subtest5
= vectors_equal(
1070 point_on_screen(1, 1, Camera2
),
1071 #vector
{x
=320, y
=240, z
=320}),
1072 Subtest6
= vectors_equal(
1073 point_on_screen(0.5, 0.5, Camera2
),
1074 #vector
{x
=0, y
=0, z
=320}),
1076 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
1078 nearest_object_intersecting_ray_test() ->
1079 io:format("nearest object intersecting ray test", []),
1080 % test to make sure that we really get the closest object
1081 Sphere1
=#sphere
{radius
=5,
1082 center
=#vector
{x
=0, y
=0, z
=10},
1084 colour
=#colour
{r
=0, g
=0, b
=0.03}}},
1085 Sphere2
=#sphere
{radius
=5,
1086 center
=#vector
{x
=0, y
=0, z
=20},
1088 colour
=#colour
{r
=0, g
=0, b
=0.06}}},
1089 Sphere3
=#sphere
{radius
=5,
1090 center
=#vector
{x
=0, y
=0, z
=30},
1092 colour
=#colour
{r
=0, g
=0, b
=0.09}}},
1093 Sphere4
=#sphere
{radius
=5,
1094 center
=#vector
{x
=0, y
=0, z
=-10},
1096 colour
=#colour
{r
=0, g
=0, b
=-0.4}}},
1097 Scene1
=[Sphere1
, Sphere2
, Sphere3
, Sphere4
],
1098 Ray1
=#ray
{origin
=#vector
{x
=0, y
=0, z
=0},
1099 direction
=#vector
{x
=0, y
=0, z
=1}},
1101 {Object1
, Distance1
, Hit_location
, Normal
} = nearest_object_intersecting_ray(
1103 Subtest1
= (Object1
== Sphere1
) and (Distance1
== 5)
1104 and
vectors_equal(Normal
, vector_neg(Ray1#ray
.direction
))
1105 and
point_on_sphere(Sphere1
, Hit_location
),
1109 focal_length_test() ->
1112 io:format("focal length test", []),
1114 fun({Focal_length
, Dimension
}, Matches
) ->
1115 %Result = focal_length(Dimension, Size),
1116 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
1118 and ((Focal_length
+ Epsilon
>= focal_length(
1120 and (Focal_length
- Epsilon
=< focal_length(
1123 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
1125 vector_bounce_off_plane_test() ->
1126 io:format("vector reflect about normal", []),
1127 Vector1
= #vector
{x
=1, y
=1, z
=0},
1128 Vector2
= #vector
{x
=0, y
=-1, z
=0},
1129 Vector3
= #vector
{x
=1, y
=-1, z
=0},
1130 Vector4
= #vector
{x
=1, y
=0, z
=0},
1132 Subtest1
= vectors_equal(vector_bounce_off_plane(
1134 vector_normalize(Vector2
)),
1137 Subtest2
= vectors_equal(
1138 vector_bounce_off_plane(
1140 vector_normalize(Vector1
)),
1143 Subtest1 and Subtest2
.