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
, Object
, Scene
))));
246 (_Not_a_point_light
, Final_colour
) ->
249 #vector
{x
=0, y
=0, z
=0},
252 % returns 0 if Object is occluded from the light at Light_location, otherwise
253 % returns 1 if light can see Object
254 shadow_factor(Light_location
, Hit_location
, Object
, Scene
) ->
255 Light_vector
= vector_sub(Hit_location
, Light_location
),
256 Light_vector_length
= vector_mag(Light_vector
),
257 Light_direction
= vector_normalize(Light_vector
),
258 Shadow_ray
= #ray
{origin
=Light_location
,
259 direction
=Light_direction
},
260 case nearest_object_intersecting_ray(Shadow_ray
, Scene
) of
261 % this could match another copy of the same object
262 {Object
, Distance
, _Loc
, _Normal
} ->
269 % http://www.devmaster.net/wiki/Lambert_diffuse_lighting
270 % http://svn.icculus.org/darkwar/trunk/base/shaders/light.frag?rev=1067&view=auto
271 diffuse_term(Object
, Light_location
, Hit_location
, Hit_normal
) ->
273 colour_to_vector(object_diffuse_colour(Object
)),
275 vector_dot_product(Hit_normal
,
277 vector_sub(Light_location
,
281 % http://svn.icculus.org/darkwar/trunk/base/shaders/light.frag?rev=1067&view=auto
282 % http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_2_Phong_Mirrors_and_Shadows.shtml
283 % http://www.devmaster.net/wiki/Phong_shading
284 specular_term(EyeVector
, Light_location
, Hit_location
, Hit_normal
,
285 Specular_power
, Shininess
, Specular_colour
) ->
287 colour_to_vector(Specular_colour
),
294 vector_sub(Light_location
, Hit_location
)),
295 vector_neg(EyeVector
))),
296 Hit_normal
)]), Specular_power
)).
298 % object agnostic intersection function
299 nearest_object_intersecting_ray(Ray
, Scene
) ->
300 nearest_object_intersecting_ray(
301 Ray
, none
, hitlocation
, hitnormal
, infinity
, Scene
).
302 nearest_object_intersecting_ray(
303 _Ray
, _NearestObj
, _Hit_location
, _Normal
, infinity
, []) ->
305 nearest_object_intersecting_ray(
306 _Ray
, NearestObj
, Hit_location
, Normal
, Distance
, []) ->
307 % io:format("intersecting ~w at ~w~n", [NearestObj, Distance]),
308 {NearestObj
, Distance
, Hit_location
, Normal
};
309 nearest_object_intersecting_ray(Ray
,
314 [CurrentObject
|Rest_of_scene
]) ->
315 case ray_object_intersect(Ray
, CurrentObject
) of
316 {NewDistance
, New_hit_location
, New_normal
} ->
317 %io:format("Distace=~w NewDistace=~w~n", [Distance, NewDistance]),
318 if (Distance
== infinity
) or (Distance
> NewDistance
) ->
319 %io:format("another closer object found~n", []),
320 nearest_object_intersecting_ray(
328 %io:format("no closer obj found~n", []),
329 nearest_object_intersecting_ray(
338 nearest_object_intersecting_ray(
347 % object specific intersection function
348 ray_object_intersect(Ray
, Object
) ->
351 ray_sphere_intersect(Ray
, Object
);
353 ray_triangle_intersect(Ray
, Object
);
355 ray_plane_intersect(Ray
, Object
);
361 % http://www.devmaster.net/articles/raytracing/
362 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter1.htm
363 ray_sphere_intersect(
368 #sphere
{radius
=Radius
, center
=#vector
{
369 x
=Xc
, y
=Yc
, z
=Zc
}}) ->
371 A
= Xd
*Xd
+ Yd
*Yd
+ Zd
*Zd
,
372 B
= 2 * (Xd
*(X0
-Xc
) + Yd
*(Y0
-Yc
) + Zd
*(Z0
-Zc
)),
373 C
= (X0
-Xc
)*(X0
-Xc
) + (Y0
-Yc
)*(Y0
-Yc
) + (Z0
-Zc
)*(Z0
-Zc
) - Radius
*Radius
,
374 Discriminant
= B
*B
- 4*A
*C
,
375 %io:format("A=~w B=~w C=~w discriminant=~w~n",
376 % [A, B, C, Discriminant]),
377 if Discriminant
>= Epsilon
->
378 T0
= (-B
+ math:sqrt(Discriminant
))/2,
379 T1
= (-B
- math:sqrt(Discriminant
))/2,
380 if (T0
>= 0) and (T1
>= 0) ->
381 %io:format("T0=~w T1=~w~n", [T0, T1]),
382 Distance
= lists:min([T0
, T1
]),
383 Intersection
= vector_add(
384 #vector
{x
=X0
, y
=Y0
, z
=Z0
},
386 #vector
{x
=Xd
, y
=Yd
, z
=Zd
}, Distance
)),
387 Normal
= vector_normalize(
388 vector_sub(Intersection
,
389 #vector
{x
=Xc
, y
=Yc
, z
=Zc
})),
390 {Distance
, Intersection
, Normal
};
399 % http://www.graphics.cornell.edu/pubs/1997/MT97.html
400 % http://jgt.akpeters.com/papers/GuigueDevillers03/addendum.html
401 ray_triangle_intersect(Ray
, Triangle
) ->
404 % find vectors for two edges sharing v1
405 Edge1
= vector_sub(Triangle#triangle
.v2
, Triangle#triangle
.v1
),
406 Edge2
= vector_sub(Triangle#triangle
.v3
, Triangle#triangle
.v1
),
408 % begin calculating determinant
409 P
= vector_cross_product(Ray#ray
.direction
, Edge2
),
410 Determinant
= vector_dot_product(Edge1
, P
),
412 % negative determinant means the triangle is facing away
415 if Determinant
< Epsilon
->
416 % for our purposes we ignore such triangles
417 %% io:format("ray is either behind or on the triangle: ~p~n", [Determinant]),
420 % calculate the distance from v1 to ray origin
421 T
= vector_sub(Ray#ray
.origin
, Triangle#triangle
.v1
),
423 % calculate the U parameter and test bounds
424 U
= vector_dot_product(T
, P
),
425 if (U
< 0) or (U
> Determinant
) ->
426 %% io:format("U is negative or greater than det: ~p~n", [U]),
429 % prepare to test the V parameter
430 Q
= vector_cross_product(T
, Edge1
),
431 % calculate the V parameter and test bounds
432 V
= vector_dot_product(Ray#ray
.direction
, Q
),
433 if (V
< 0) or (U
+V
> Determinant
) ->
434 %% io:format("V less than 0.0 or U+V greater than det: ~p ~p~n",
438 % calculate the distance to the
439 % intersection point and return
440 %% io:format("found ray/triangle intersection ~n", []),
441 Distance
= vector_dot_product(Edge2
, Q
) / Determinant
,
442 Intersection
= vector_add(
447 Normal
= vector_normalize(
449 Triangle#triangle
.v1
,
450 Triangle#triangle
.v2
)),
451 {Distance
, Intersection
, Normal
}
457 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
458 % http://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm
459 % http://www.devmaster.net/articles/raytracing/
460 ray_plane_intersect(Ray
, Plane
) ->
462 Vd
= vector_dot_product(Plane#plane
.normal
, Ray#ray
.direction
),
464 V0
= -(vector_dot_product(Plane#plane
.normal
, Ray#ray
.origin
)
465 + Plane#plane
.distance
),
467 if Distance
< Epsilon
->
470 Intersection
= vector_add(
475 {Distance
, Intersection
, Plane#plane
.normal
}
482 focal_length(Angle
, Dimension
) ->
483 Dimension
/(2*math:tan(Angle
*(math:pi()/180)/2)).
485 point_on_screen(X
, Y
, Camera
) ->
486 %TODO: implement rotation (using quaternions)
487 Screen_width
= (Camera#camera
.screen
)#screen
.width
,
488 Screen_height
= (Camera#camera
.screen
)#screen
.height
,
489 lists:foldl(fun(Vect
, Sum
) -> vector_add(Vect
, Sum
) end,
490 Camera#camera
.location
,
492 #vector
{x
=0, y
=0, z
=1},
496 #vector
{x
= (X
-0.5) * Screen_width
,
500 y
= (Y
-0.5) * Screen_height
,
505 shoot_ray(From
, Through
) ->
506 #ray
{origin
=From
, direction
=vector_normalize(vector_sub(Through
, From
))}.
508 % assume that X and Y are percentages of the 3D world screen dimensions
509 ray_through_pixel(X
, Y
, Camera
) ->
510 shoot_ray(Camera#camera
.location
, point_on_screen(X
, Y
, Camera
)).
512 vectors_equal(V1
, V2
) ->
513 vectors_equal(V1
, V2
, 0.0001).
514 vectors_equal(V1
, V2
, Epsilon
) ->
515 (V1#vector
.x
+ Epsilon
>= V2#vector
.x
)
516 and (V1#vector
.x
- Epsilon
=<V2#vector
.x
)
517 and (V1#vector
.y
+ Epsilon
>= V2#vector
.y
)
518 and (V1#vector
.y
- Epsilon
=<V2#vector
.y
)
519 and (V1#vector
.z
+ Epsilon
>= V2#vector
.z
)
520 and (V1#vector
.z
- Epsilon
=<V2#vector
.z
).
523 vector_add(V1
, V2
) ->
524 #vector
{x
= V1#vector
.x
+ V2#vector
.x
,
525 y
= V1#vector
.y
+ V2#vector
.y
,
526 z
= V1#vector
.z
+ V2#vector
.z
}.
528 vector_sub(V1
, V2
) ->
529 #vector
{x
= V1#vector
.x
- V2#vector
.x
,
530 y
= V1#vector
.y
- V2#vector
.y
,
531 z
= V1#vector
.z
- V2#vector
.z
}.
533 vector_square_mag(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
537 math:sqrt(vector_square_mag(V
)).
539 vector_scalar_mult(#vector
{x
=X
, y
=Y
, z
=Z
}, Scalar
) ->
540 #vector
{x
=X
*Scalar
, y
=Y
*Scalar
, z
=Z
*Scalar
}.
542 vector_component_mult(#vector
{x
=X1
, y
=Y1
, z
=Z1
}, #vector
{x
=X2
, y
=Y2
, z
=Z2
}) ->
543 #vector
{x
=X1
*X2
, y
=Y1
*Y2
, z
=Z1
*Z2
}.
545 vector_dot_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
546 A1
*B1
+ A2
*B2
+ A3
*B3
.
548 vector_cross_product(#vector
{x
=A1
, y
=A2
, z
=A3
}, #vector
{x
=B1
, y
=B2
, z
=B3
}) ->
549 #vector
{x
= A2
*B3
- A3
*B2
,
553 vector_normalize(V
) ->
556 #vector
{x
=0, y
=0, z
=0};
558 vector_scalar_mult(V
, 1/vector_mag(V
))
561 vector_neg(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
562 #vector
{x
=-X
, y
=-Y
, z
=-Z
}.
565 % http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtreflec.htm
566 % http://www.devmaster.net/articles/raytracing/
567 vector_bounce_off_plane(Vector
, Normal
) ->
571 2*vector_dot_product(Normal
, vector_neg(Vector
))),
574 object_diffuse_colour(#sphere
{material
=#material
{colour
=C
}}) ->
576 object_diffuse_colour(#plane
{material
=#material
{colour
=C
}}) ->
578 object_diffuse_colour(#triangle
{material
=#material
{colour
=C
}}) ->
581 object_specular_power(#sphere
{material
=#material
{specular_power
=SP
}}) ->
583 object_specular_power(#plane
{material
=#material
{specular_power
=SP
}}) ->
585 object_specular_power(#triangle
{material
=#material
{specular_power
=SP
}}) ->
588 object_shininess(#sphere
{material
=#material
{shininess
=S
}}) ->
590 object_shininess(#plane
{material
=#material
{shininess
=S
}}) ->
592 object_shininess(#triangle
{material
=#material
{shininess
=S
}}) ->
595 object_reflectivity(#sphere
{material
=#material
{reflectivity
=R
}}) ->
597 object_reflectivity(#plane
{material
=#material
{reflectivity
=R
}}) ->
599 object_reflectivity(#triangle
{material
=#material
{reflectivity
=R
}}) ->
602 point_on_sphere(#sphere
{radius
=Radius
, center
=#vector
{x
=XC
, y
=YC
, z
=ZC
}},
603 #vector
{x
=X
, y
=Y
, z
=Z
}) ->
606 ((X
-XC
)*(X
-XC
) + (Y
-YC
)*(Y
-YC
) + (Z
-ZC
)*(Z
-ZC
)) - Radius
*Radius
).
608 colour_to_vector(#colour
{r
=R
, g
=G
, b
=B
}) ->
609 #vector
{x
=R
, y
=G
, z
=B
}.
610 vector_to_colour(#vector
{x
=X
, y
=Y
, z
=Z
}) ->
611 #colour
{r
=X
, g
=Y
, b
=Z
}.
612 colour_to_pixel(#colour
{r
=R
, g
=G
, b
=B
}) ->
615 % returns a list of objects in the scene
616 % camera is assumed to be the first element in the scene
618 [#camera
{location
=#vector
{x
=0, y
=0, z
=-2},
619 rotation
=#vector
{x
=0, y
=0, z
=0},
621 screen
=#screen
{width
=4, height
=3}},
622 #point_light
{diffuse_colour
=#colour
{r
=1, g
=1, b
=0.5},
623 location
=#vector
{x
=5, y
=-2, z
=0},
624 specular_colour
=#colour
{r
=1, g
=1, b
=1}},
625 #point_light
{diffuse_colour
=#colour
{r
=1, g
=0, b
=0.5},
626 location
=#vector
{x
=-10, y
=0, z
=7},
627 specular_colour
=#colour
{r
=1, g
=0, b
=0.5}},
629 center
=#vector
{x
=4, y
=0, z
=10},
631 colour
=#colour
{r
=0, g
=0.5, b
=1},
636 center
=#vector
{x
=-5, y
=3, z
=9},
638 colour
=#colour
{r
=1, g
=0.5, b
=0},
643 center
=#vector
{x
=-4.5, y
=-2.5, z
=14},
645 colour
=#colour
{r
=0.5, g
=1, b
=0},
649 #triangle
{v1
=#vector
{x
=-2, y
=5, z
=5},
650 v2
=#vector
{x
=4, y
=5, z
=10},
651 v3
=#vector
{x
=4, y
=-5, z
=10},
653 colour
=#colour
{r
=1, g
=0.5, b
=0},
657 #plane
{normal
=#vector
{x
=0, y
=-1, z
=0},
660 colour
=#colour
{r
=1, g
=1, b
=1},
666 % assumes Pixels are ordered in a row by row fasion
667 write_pixels_to_ppm(Width
, Height
, MaxValue
, Pixels
, Filename
) ->
668 case file:open(Filename
, write
) of
670 io:format("file opened~n", []),
671 io:format(IoDevice
, "P3~n", []),
672 io:format(IoDevice
, "~p ~p~n", [Width
, Height
]),
673 io:format(IoDevice
, "~p~n", [MaxValue
]),
675 fun({_Num
, {R
, G
, B
}}) ->
676 io:format(IoDevice
, "~p ~p ~p ",
677 [lists:min([trunc(R
*MaxValue
), MaxValue
]),
678 lists:min([trunc(G
*MaxValue
), MaxValue
]),
679 lists:min([trunc(B
*MaxValue
), MaxValue
])]) end,
681 file:close(IoDevice
);
683 io:format("error opening file~n", [])
686 % various invocation style functions
687 standalone([Width
, Height
, Filename
, Recursion_depth
, Strategy
]) ->
688 standalone(list_to_integer(Width
),
689 list_to_integer(Height
),
691 list_to_integer(Recursion_depth
),
692 tracing_function(list_to_atom(Strategy
))).
694 standalone(Width
, Height
, Filename
, Recursion_depth
, Function
) ->
695 {Time
, _Value
} = timer:tc(
703 io:format("Done in ~w seconds~n", [Time
/1000000]),
707 raytrace(tracing_function(Strategy
)).
709 go(Width
, Height
, Filename
, Recursion_depth
, Strategy
) ->
710 raytrace(Width
, Height
, Filename
, Recursion_depth
,
711 tracing_function(Strategy
)).
713 tracing_function(simple
) ->
714 fun raytraced_pixel_list_simple
/4;
715 tracing_function(concurrent
) ->
716 fun raytraced_pixel_list_concurrent
/4;
717 tracing_function(distributed
) ->
718 fun raytraced_pixel_list_distributed
/4.
720 raytrace(Function
) ->
721 raytrace(4, 3, "/tmp/traced.ppm", 5, Function
).
722 raytrace(Width
, Height
, Filename
, Recursion_depth
, Function
) ->
736 Tests
= [fun scene_test
/0,
738 fun vector_equality_test
/0,
739 fun vector_addition_test
/0,
740 fun vector_subtraction_test
/0,
741 fun vector_square_mag_test
/0,
742 fun vector_mag_test
/0,
743 fun vector_scalar_multiplication_test
/0,
744 fun vector_dot_product_test
/0,
745 fun vector_cross_product_test
/0,
746 fun vector_normalization_test
/0,
747 fun vector_negation_test
/0,
748 % fun ray_through_pixel_test/0,
749 fun ray_shooting_test
/0,
750 fun point_on_screen_test
/0,
751 fun nearest_object_intersecting_ray_test
/0,
752 fun focal_length_test
/0,
753 % fun vector_rotation_test/0,
754 fun vector_bounce_off_plane_test
/0,
755 fun ray_sphere_intersection_test
/0
757 run_tests(Tests
, 1, true
).
760 io:format("testing the scene function", []),
774 {colour
, 1, 0, 0.5}},
778 {material
, {colour
, 0, 0.5, 1}, 20, 1, 0.1}},
782 {material
, {colour
, 1, 0.5, 0}, 4, 0.25, 0.5}},
785 {vector
, -4.5, -2.5, 14},
786 {material
, {colour
, 0.5, 1, 0}, 20, 0.25, 0.7}},
791 {material
, {colour
, 1, 0.5, 0}, 4, 0.25, 0.5}},
795 {material
, {colour
, 1, 1, 1}, 1, 0, 0.01}}
803 io:format("this test always passes", []),
806 run_tests([], _Num
, Success
) ->
809 io:format("Success!~n", []),
812 io:format("some tests failed~n", []),
816 run_tests([First_test
|Rest_of_tests
], Num
, Success_so_far
) ->
817 io:format("test #~p: ", [Num
]),
818 Current_success
= First_test(),
819 case Current_success
of
821 io:format(" - OK~n", []);
823 io:format(" - FAILED~n", [])
825 run_tests(Rest_of_tests
, Num
+ 1, Current_success and Success_so_far
).
827 vector_equality_test() ->
828 io:format("vector equality"),
829 Vector1
= #vector
{x
=0, y
=0, z
=0},
830 Vector2
= #vector
{x
=1234, y
=-234, z
=0},
831 Vector3
= #vector
{x
=0.0983, y
=0.0214, z
=0.12342},
832 Vector4
= #vector
{x
=0.0984, y
=0.0213, z
=0.12341},
833 Vector5
= #vector
{x
=10/3, y
=-10/6, z
=8/7},
834 Vector6
= #vector
{x
=3.3, y
=-1.6, z
=1.1},
836 Subtest1
= vectors_equal(Vector1
, Vector1
)
837 and
vectors_equal(Vector2
, Vector2
)
838 and
not (vectors_equal(Vector1
, Vector2
))
839 and
not (vectors_equal(Vector2
, Vector1
)),
840 Subtest2
= vectors_equal(Vector3
, Vector4
, 0.0001),
841 Subtest3
= vectors_equal(Vector5
, Vector6
, 0.1),
843 Subtest1 and Subtest2 and Subtest3
.
846 vector_addition_test() ->
847 io:format("vector addition", []),
848 Vector0
= vector_add(
849 #vector
{x
=3, y
=7, z
=-3},
850 #vector
{x
=0, y
=-24, z
=123}),
851 Subtest1
= (Vector0#vector
.x
== 3)
852 and (Vector0#vector
.y
== -17)
853 and (Vector0#vector
.z
== 120),
855 Vector1
= #vector
{x
=5, y
=0, z
=984},
856 Vector2
= vector_add(Vector1
, Vector1
),
857 Subtest2
= (Vector2#vector
.x
== Vector1#vector
.x
*2)
858 and (Vector2#vector
.y
== Vector1#vector
.y
*2)
859 and (Vector2#vector
.z
== Vector1#vector
.z
*2),
861 Vector3
= #vector
{x
=908, y
=-098, z
=234},
862 Vector4
= vector_add(Vector3
, #vector
{x
=0, y
=0, z
=0}),
863 Subtest3
= vectors_equal(Vector3
, Vector4
),
865 Subtest1 and Subtest2 and Subtest3
.
867 vector_subtraction_test() ->
868 io:format("vector subtraction", []),
869 Vector1
= #vector
{x
=0, y
=0, z
=0},
870 Vector2
= #vector
{x
=8390, y
=-2098, z
=939},
871 Vector3
= #vector
{x
=1, y
=1, z
=1},
872 Vector4
= #vector
{x
=-1, y
=-1, z
=-1},
874 Subtest1
= vectors_equal(Vector1
, vector_sub(Vector1
, Vector1
)),
875 Subtest2
= vectors_equal(Vector3
, vector_sub(Vector3
, Vector1
)),
876 Subtest3
= not
vectors_equal(Vector3
, vector_sub(Vector1
, Vector3
)),
877 Subtest4
= vectors_equal(Vector4
, vector_sub(Vector4
, Vector1
)),
878 Subtest5
= not
vectors_equal(Vector4
, vector_sub(Vector1
, Vector4
)),
879 Subtest5
= vectors_equal(vector_add(Vector2
, Vector4
),
880 vector_sub(Vector2
, Vector3
)),
882 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5
.
884 vector_square_mag_test() ->
885 io:format("vector square magnitude test", []),
886 Vector1
= #vector
{x
=0, y
=0, z
=0},
887 Vector2
= #vector
{x
=1, y
=1, z
=1},
888 Vector3
= #vector
{x
=3, y
=-4, z
=0},
890 Subtest1
= (0 == vector_square_mag(Vector1
)),
891 Subtest2
= (3 == vector_square_mag(Vector2
)),
892 Subtest3
= (25 == vector_square_mag(Vector3
)),
894 Subtest1 and Subtest2 and Subtest3
.
897 io:format("vector magnitude test", []),
898 Vector1
= #vector
{x
=0, y
=0, z
=0},
899 Vector2
= #vector
{x
=1, y
=1, z
=1},
900 Vector3
= #vector
{x
=3, y
=-4, z
=0},
902 Subtest1
= (0 == vector_mag(Vector1
)),
903 Subtest2
= (math:sqrt(3) == vector_mag(Vector2
)),
904 Subtest3
= (5 == vector_mag(Vector3
)),
906 Subtest1 and Subtest2 and Subtest3
.
908 vector_scalar_multiplication_test() ->
909 io:format("scalar multiplication test", []),
910 Vector1
= #vector
{x
=0, y
=0, z
=0},
911 Vector2
= #vector
{x
=1, y
=1, z
=1},
912 Vector3
= #vector
{x
=3, y
=-4, z
=0},
914 Subtest1
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, 45)),
915 Subtest2
= vectors_equal(Vector1
, vector_scalar_mult(Vector1
, -13)),
916 Subtest3
= vectors_equal(Vector1
, vector_scalar_mult(Vector3
, 0)),
917 Subtest4
= vectors_equal(#vector
{x
=4, y
=4, z
=4},
918 vector_scalar_mult(Vector2
, 4)),
919 Subtest5
= vectors_equal(Vector3
, vector_scalar_mult(Vector3
, 1)),
920 Subtest6
= not
vectors_equal(Vector3
, vector_scalar_mult(Vector3
, -3)),
922 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
924 vector_dot_product_test() ->
925 io:format("dot product test", []),
926 Vector1
= #vector
{x
=1, y
=3, z
=-5},
927 Vector2
= #vector
{x
=4, y
=-2, z
=-1},
928 Vector3
= #vector
{x
=0, y
=0, z
=0},
929 Vector4
= #vector
{x
=1, y
=0, z
=0},
930 Vector5
= #vector
{x
=0, y
=1, z
=0},
932 Subtest1
= 3 == vector_dot_product(Vector1
, Vector2
),
933 Subtest2
= vector_dot_product(Vector2
, Vector2
)
934 == vector_square_mag(Vector2
),
935 Subtest3
= 0 == vector_dot_product(Vector3
, Vector1
),
936 Subtest4
= 0 == vector_dot_product(Vector4
, Vector5
),
938 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
940 vector_cross_product_test() ->
941 io:format("cross product test", []),
942 Vector1
= #vector
{x
=0, y
=0, z
=0},
943 Vector2
= #vector
{x
=1, y
=0, z
=0},
944 Vector3
= #vector
{x
=0, y
=1, z
=0},
945 Vector4
= #vector
{x
=0, y
=0, z
=1},
946 Vector5
= #vector
{x
=1, y
=2, z
=3},
947 Vector6
= #vector
{x
=4, y
=5, z
=6},
948 Vector7
= #vector
{x
=-3, y
=6, z
=-3},
949 Vector8
= #vector
{x
=-1, y
=0, z
=0},
950 Vector9
= #vector
{x
=-9, y
=8, z
=433},
952 Subtest1
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector2
)),
953 Subtest2
= vectors_equal(Vector1
, vector_cross_product(Vector2
, Vector8
)),
954 Subtest3
= vectors_equal(Vector2
, vector_cross_product(Vector3
, Vector4
)),
955 Subtest4
= vectors_equal(Vector7
, vector_cross_product(Vector5
, Vector6
)),
956 Subtest5
= vectors_equal(
957 vector_cross_product(Vector7
,
958 vector_add(Vector8
, Vector9
)),
960 vector_cross_product(Vector7
, Vector8
),
961 vector_cross_product(Vector7
, Vector9
))),
962 Subtest6
= vectors_equal(Vector1
,
965 vector_cross_product(
967 vector_cross_product(Vector8
, Vector9
)),
968 vector_cross_product(
970 vector_cross_product(Vector9
, Vector7
))),
971 vector_cross_product(
973 vector_cross_product(Vector7
, Vector8
)))),
975 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
977 vector_normalization_test() ->
978 io:format("normalization test", []),
979 Vector1
= #vector
{x
=0, y
=0, z
=0},
980 Vector2
= #vector
{x
=1, y
=0, z
=0},
981 Vector3
= #vector
{x
=5, y
=0, z
=0},
983 Subtest1
= vectors_equal(Vector1
, vector_normalize(Vector1
)),
984 Subtest2
= vectors_equal(Vector2
, vector_normalize(Vector2
)),
985 Subtest3
= vectors_equal(Vector2
, vector_normalize(Vector3
)),
986 Subtest4
= vectors_equal(Vector2
, vector_normalize(
987 vector_scalar_mult(Vector2
, 324))),
989 Subtest1 and Subtest2 and Subtest3 and Subtest4
.
991 vector_negation_test() ->
992 io:format("vector negation test", []),
993 Vector1
= #vector
{x
=0, y
=0, z
=0},
994 Vector2
= #vector
{x
=4, y
=-5, z
=6},
996 Subtest1
= vectors_equal(Vector1
, vector_neg(Vector1
)),
997 Subtest2
= vectors_equal(Vector2
, vector_neg(vector_neg(Vector2
))),
999 Subtest1 and Subtest2
.
1001 ray_shooting_test() ->
1002 io:format("ray shooting test"),
1003 Vector1
= #vector
{x
=0, y
=0, z
=0},
1004 Vector2
= #vector
{x
=1, y
=0, z
=0},
1006 Subtest1
= vectors_equal(
1007 (shoot_ray(Vector1
, Vector2
))#ray
.direction
,
1012 ray_sphere_intersection_test() ->
1013 io:format("ray sphere intersection test", []),
1017 center
=#vector
{x
= 0, y
=0, z
=10},
1019 colour
=#colour
{r
=0.4, g
=0.4, b
=0.4}}},
1021 origin
=#vector
{x
=0, y
=0, z
=0},
1022 direction
=#vector
{x
=0, y
=0, z
=1}},
1024 origin
=#vector
{x
=3, y
=0, z
=0},
1025 direction
=#vector
{x
=0, y
=0, z
=1}},
1027 origin
=#vector
{x
=4, y
=0, z
=0},
1028 direction
=#vector
{x
=0, y
=0, z
=1}},
1029 {Distance1
, _Hit_location1
, _Hit_normal1
} = ray_sphere_intersect(Ray1
, Sphere
),
1030 Subtest1
= Distance1
== 7.0,
1031 Subtest2
= ray_sphere_intersect(Ray2
, Sphere
) == none
,
1032 Subtest3
= ray_sphere_intersect(Ray3
, Sphere
) == none
,
1033 Subtest1 and Subtest2 and Subtest3
.
1035 point_on_screen_test() ->
1036 io:format("point on screen test", []),
1037 Camera1
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
1038 rotation
=#vector
{x
=0, y
=0, z
=0},
1040 screen
=#screen
{width
=1, height
=1}},
1041 Camera2
= #camera
{location
=#vector
{x
=0, y
=0, z
=0},
1042 rotation
=#vector
{x
=0, y
=0, z
=0},
1044 screen
=#screen
{width
=640, height
=480}},
1046 Subtest1
= vectors_equal(
1047 #vector
{x
=0, y
=0, z
=0.5},
1048 point_on_screen(0.5, 0.5, Camera1
)),
1049 Subtest2
= vectors_equal(
1050 #vector
{x
=-0.5, y
=-0.5, z
=0.5},
1051 point_on_screen(0, 0, Camera1
)),
1052 Subtest3
= vectors_equal(
1053 #vector
{x
=0.5, y
=0.5, z
=0.5},
1054 point_on_screen(1, 1, Camera1
)),
1055 Subtest4
= vectors_equal(
1056 point_on_screen(0, 0, Camera2
),
1057 #vector
{x
=-320, y
=-240, z
=320}),
1058 Subtest5
= vectors_equal(
1059 point_on_screen(1, 1, Camera2
),
1060 #vector
{x
=320, y
=240, z
=320}),
1061 Subtest6
= vectors_equal(
1062 point_on_screen(0.5, 0.5, Camera2
),
1063 #vector
{x
=0, y
=0, z
=320}),
1065 Subtest1 and Subtest2 and Subtest3 and Subtest4 and Subtest5 and Subtest6
.
1067 nearest_object_intersecting_ray_test() ->
1068 io:format("nearest object intersecting ray test", []),
1069 % test to make sure that we really get the closest object
1070 Sphere1
=#sphere
{radius
=5,
1071 center
=#vector
{x
=0, y
=0, z
=10},
1073 colour
=#colour
{r
=0, g
=0, b
=0.03}}},
1074 Sphere2
=#sphere
{radius
=5,
1075 center
=#vector
{x
=0, y
=0, z
=20},
1077 colour
=#colour
{r
=0, g
=0, b
=0.06}}},
1078 Sphere3
=#sphere
{radius
=5,
1079 center
=#vector
{x
=0, y
=0, z
=30},
1081 colour
=#colour
{r
=0, g
=0, b
=0.09}}},
1082 Sphere4
=#sphere
{radius
=5,
1083 center
=#vector
{x
=0, y
=0, z
=-10},
1085 colour
=#colour
{r
=0, g
=0, b
=-0.4}}},
1086 Scene1
=[Sphere1
, Sphere2
, Sphere3
, Sphere4
],
1087 Ray1
=#ray
{origin
=#vector
{x
=0, y
=0, z
=0},
1088 direction
=#vector
{x
=0, y
=0, z
=1}},
1090 {Object1
, Distance1
, Hit_location
, Normal
} = nearest_object_intersecting_ray(
1092 Subtest1
= (Object1
== Sphere1
) and (Distance1
== 5)
1093 and
vectors_equal(Normal
, vector_neg(Ray1#ray
.direction
))
1094 and
point_on_sphere(Sphere1
, Hit_location
),
1098 focal_length_test() ->
1101 io:format("focal length test", []),
1103 fun({Focal_length
, Dimension
}, Matches
) ->
1104 %Result = focal_length(Dimension, Size),
1105 %io:format("comparing ~w ~w ~w ~w~n", [Focal_length, Dimension, Result, Matches]),
1107 and ((Focal_length
+ Epsilon
>= focal_length(
1109 and (Focal_length
- Epsilon
=< focal_length(
1112 [{13, 108}, {15, 100.4}, {18, 90}, {21, 81.2}]).
1114 vector_bounce_off_plane_test() ->
1115 io:format("vector reflect about normal", []),
1116 Vector1
= #vector
{x
=1, y
=1, z
=0},
1117 Vector2
= #vector
{x
=0, y
=-1, z
=0},
1118 Vector3
= #vector
{x
=1, y
=-1, z
=0},
1119 Vector4
= #vector
{x
=1, y
=0, z
=0},
1121 Subtest1
= vectors_equal(vector_bounce_off_plane(
1123 vector_normalize(Vector2
)),
1126 Subtest2
= vectors_equal(
1127 vector_bounce_off_plane(
1129 vector_normalize(Vector1
)),
1132 Subtest1 and Subtest2
.